Homework Data Visualization¶
- Nama: I Gusti Ngurah Agung Hari Vijaya Kusuma
- Cita-cita semasa kecil: Astronot
Soal 1¶
Buatlah grafik yang memuat informasi rata-rata biaya bulanan berdasarkan metode pembayarannya! Kemudian tuliskan interpretasi-mu dari grafik tersebut. (10 poin)
Langkah-langkah:
- Kelompokkan data berdasarkan metode pembayar an dan gunakan fungsi agregasi yang tepat untuk kasus ini.
- Kemudian, buatlah grafik yang tepat menurutmu
Untuk mempermudah kamu, yuk lihat resource di bawah ini:
- Topic Data Processing: Materi DataFrame Aggregation - bagian Groupby
Jawaban Soal 1
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
# versi pandas, matplotlib, seaborn device agung
print("matplotlib version:", matplotlib.__version__)
print("pandas version:", pd.__version__)
print("seaborn version:", sns.__version__)
matplotlib version: 3.9.4 pandas version: 2.3.0+4.g1dfc98e16a seaborn version: 0.13.2
# Import dataset ke dataframe agung
agung = pd.read_csv('Homework_Dataset.csv')
# tampilkan agung isinya
agung.head()
| customerID | gender | SeniorCitizen | Partner | Dependents | tenure | PhoneService | MultipleLines | InternetService | OnlineSecurity | ... | DeviceProtection | TechSupport | StreamingTV | StreamingMovies | Contract | PaperlessBilling | PaymentMethod | MonthlyCharges | TotalCharges | Churn | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 7590-VHVEG | Female | 0 | Yes | No | 1 | No | No phone service | DSL | No | ... | No | No | No | No | Month-to-month | Yes | Electronic check | 29.85 | 29.85 | No |
| 1 | 5575-GNVDE | Male | 0 | No | No | 34 | Yes | No | DSL | Yes | ... | Yes | No | No | No | One year | No | Mailed check | 56.95 | 1889.5 | No |
| 2 | 3668-QPYBK | Male | 0 | No | No | 2 | Yes | No | DSL | Yes | ... | No | No | No | No | Month-to-month | Yes | Mailed check | 53.85 | 108.15 | Yes |
| 3 | 7795-CFOCW | Male | 0 | No | No | 45 | No | No phone service | DSL | Yes | ... | Yes | Yes | No | No | One year | No | Bank transfer (automatic) | 42.30 | 1840.75 | No |
| 4 | 9237-HQITU | Female | 0 | No | No | 2 | Yes | No | Fiber optic | No | ... | No | No | No | No | Month-to-month | Yes | Electronic check | 70.70 | 151.65 | Yes |
5 rows × 21 columns
#analisa dikit
agung.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 7043 entries, 0 to 7042 Data columns (total 21 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 customerID 7043 non-null object 1 gender 7043 non-null object 2 SeniorCitizen 7043 non-null int64 3 Partner 7043 non-null object 4 Dependents 7043 non-null object 5 tenure 7043 non-null int64 6 PhoneService 7043 non-null object 7 MultipleLines 7043 non-null object 8 InternetService 7043 non-null object 9 OnlineSecurity 7043 non-null object 10 OnlineBackup 7043 non-null object 11 DeviceProtection 7043 non-null object 12 TechSupport 7043 non-null object 13 StreamingTV 7043 non-null object 14 StreamingMovies 7043 non-null object 15 Contract 7043 non-null object 16 PaperlessBilling 7043 non-null object 17 PaymentMethod 7043 non-null object 18 MonthlyCharges 7043 non-null float64 19 TotalCharges 7043 non-null object 20 Churn 7043 non-null object dtypes: float64(1), int64(2), object(18) memory usage: 1.1+ MB
agung['MonthlyCharges'].describe()
count 7043.000000 mean 64.761692 std 30.090047 min 18.250000 25% 35.500000 50% 70.350000 75% 89.850000 max 118.750000 Name: MonthlyCharges, dtype: float64
agung['PaymentMethod'].unique()
array(['Electronic check', 'Mailed check', 'Bank transfer (automatic)',
'Credit card (automatic)'], dtype=object)
gemeter = agung.groupby('PaymentMethod')['MonthlyCharges'].mean().reset_index()
gemeter
| PaymentMethod | MonthlyCharges | |
|---|---|---|
| 0 | Bank transfer (automatic) | 67.192649 |
| 1 | Credit card (automatic) | 66.512385 |
| 2 | Electronic check | 76.255814 |
| 3 | Mailed check | 43.917060 |
gemeter2 = agung.groupby('PaymentMethod')['MonthlyCharges'].median().reset_index()
gemeter2
| PaymentMethod | MonthlyCharges | |
|---|---|---|
| 0 | Bank transfer (automatic) | 73.100 |
| 1 | Credit card (automatic) | 73.025 |
| 2 | Electronic check | 80.550 |
| 3 | Mailed check | 34.700 |
# urutan kategori berdasarkan rata-rata untuk plot
mean_order = gemeter.groupby('PaymentMethod')['MonthlyCharges'].mean().sort_values(ascending=False).index
# urutan kategori berdasarkan median untuk plot
median_order = gemeter2.groupby('PaymentMethod')['MonthlyCharges'].median().sort_values(ascending=False).index
sns.barplot(x='PaymentMethod', y='MonthlyCharges', data=gemeter, order=mean_order)
plt.title('Rata-rata MonthlyCharges per PaymentMethod')
plt.xticks(rotation=45)
([0, 1, 2, 3], [Text(0, 0, 'Electronic check'), Text(1, 0, 'Bank transfer (automatic)'), Text(2, 0, 'Credit card (automatic)'), Text(3, 0, 'Mailed check')])
sns.barplot(x='PaymentMethod', y='MonthlyCharges', data=gemeter2, order=median_order)
plt.title('Median MonthlyCharges per PaymentMethod')
plt.xticks(rotation=45)
([0, 1, 2, 3], [Text(0, 0, 'Electronic check'), Text(1, 0, 'Bank transfer (automatic)'), Text(2, 0, 'Credit card (automatic)'), Text(3, 0, 'Mailed check')])
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(8, 5))
sns.barplot(x='PaymentMethod', y='MonthlyCharges', data=gemeter, ax=axes[0], hue='PaymentMethod', order=mean_order, palette='deep')
sns.barplot(x='PaymentMethod', y='MonthlyCharges', data=gemeter2, ax=axes[1], hue='PaymentMethod', order=median_order, palette='deep')
axes[0].set_title('Rata-rata MonthlyCharges per PaymentMethod', fontsize=10, fontweight='bold')
axes[0].set_ylim(0, 85)
axes[0].tick_params(axis='x', rotation=40, labelsize=8)
axes[1].set_title('Median MonthlyCharges per PaymentMethod', fontsize=10, fontweight='bold')
axes[1].set_ylim(0, 85)
axes[1].set_ylabel("")
axes[1].tick_params(axis='x', rotation=40, labelsize=8)
plt.tight_layout()
plt.show()
#gpt is the way
import matplotlib.patches as mpatches
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(8, 5))
colors = sns.color_palette('deep')
payment_methods = ['Bank transfer (automatic)', 'Credit card (automatic)', 'Electronic check', 'Mailed check']
palette_dict = dict(zip(payment_methods, colors))
# rata-rata
sns.barplot(x='PaymentMethod', y='MonthlyCharges', data=gemeter, ax=axes[0], hue='PaymentMethod', order=mean_order, palette=palette_dict)
# median
sns.barplot(x='PaymentMethod', y='MonthlyCharges', data=gemeter2, ax=axes[1], hue='PaymentMethod', order=median_order, palette=palette_dict)
axes[0].set_title('Rata-rata MonthlyCharges per PaymentMethod', fontsize=10, fontweight='bold')
axes[0].set_ylim(0, 85)
axes[0].set_xticks([])
axes[1].set_title('Median MonthlyCharges per PaymentMethod', fontsize=10, fontweight='bold')
axes[1].set_ylim(0, 85)
axes[1].set_ylabel("")
axes[1].set_xticks([])
legend_patches = [mpatches.Patch(color=palette_dict[pm], label=pm) for pm in payment_methods]
axes[1].legend(handles=legend_patches, loc='lower right', title='Payment Method', fontsize=8, title_fontsize=10)
plt.tight_layout()
plt.show()
agung.groupby('PaymentMethod')['MonthlyCharges'].describe()
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| PaymentMethod | ||||||||
| Bank transfer (automatic) | 1544.0 | 67.192649 | 30.555200 | 18.40 | 41.0875 | 73.100 | 92.9625 | 118.75 |
| Credit card (automatic) | 1522.0 | 66.512385 | 30.612424 | 18.25 | 38.6625 | 73.025 | 90.8500 | 117.50 |
| Electronic check | 2365.0 | 76.255814 | 24.053655 | 18.85 | 60.1500 | 80.550 | 94.7000 | 118.65 |
| Mailed check | 1612.0 | 43.917060 | 26.314665 | 18.70 | 20.1500 | 34.700 | 64.9125 | 118.60 |
#q1 dan q3
agung.groupby('PaymentMethod')['MonthlyCharges'].agg(lambda x: (x.quantile(0.25), x.quantile(0.75)))
PaymentMethod Bank transfer (automatic) (41.0875, 92.9625) Credit card (automatic) (38.6625, 90.85) Electronic check (60.15, 94.7) Mailed check (20.15, 64.91250000000001) Name: MonthlyCharges, dtype: object
#mengecek upper bound dan lower bound untuk masing masing PaymentMethod
def calculate_bounds(group):
q1 = group['MonthlyCharges'].quantile(0.25)
q3 = group['MonthlyCharges'].quantile(0.75)
iqr = q3 - q1
lower_bound = q1 - 1.5 * iqr
upper_bound = q3 + 1.5 * iqr
return pd.Series({'Lower Bound': lower_bound, 'Upper Bound': upper_bound})
bounds = agung.groupby('PaymentMethod').apply(calculate_bounds).reset_index()
print(bounds)
PaymentMethod Lower Bound Upper Bound 0 Bank transfer (automatic) -36.72500 170.77500 1 Credit card (automatic) -39.61875 169.13125 2 Electronic check 8.32500 146.52500 3 Mailed check -46.99375 132.05625
C:\Users\Wahgus\AppData\Local\Temp\ipykernel_19720\704640460.py:10: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
bounds = agung.groupby('PaymentMethod').apply(calculate_bounds).reset_index()
#histogram untuk melihat distribusi MonthlyCharges per masing-masing PaymentMethod
for method in agung['PaymentMethod'].unique():
plt.figure(figsize=(8, 4))
sns.histplot(agung[agung['PaymentMethod'] == method]['MonthlyCharges'], bins=30, kde=True)
plt.title(f'Distribusi MonthlyCharges untuk {method}')
plt.xlabel('MonthlyCharges')
plt.ylabel('Frekuensi')
plt.show()
agung.groupby('PaymentMethod')['MonthlyCharges'].describe()
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| PaymentMethod | ||||||||
| Bank transfer (automatic) | 1544.0 | 67.192649 | 30.555200 | 18.40 | 41.0875 | 73.100 | 92.9625 | 118.75 |
| Credit card (automatic) | 1522.0 | 66.512385 | 30.612424 | 18.25 | 38.6625 | 73.025 | 90.8500 | 117.50 |
| Electronic check | 2365.0 | 76.255814 | 24.053655 | 18.85 | 60.1500 | 80.550 | 94.7000 | 118.65 |
| Mailed check | 1612.0 | 43.917060 | 26.314665 | 18.70 | 20.1500 | 34.700 | 64.9125 | 118.60 |
#box plot
plt.figure(figsize=(6, 7))
sns.boxplot(x='PaymentMethod', y='MonthlyCharges', data=agung)
plt.title('Monthly Charges by Payment Method')
plt.xlabel('Payment Method')
plt.ylabel('Monthly Charges')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
Interpretasi¶
Menurut saya grafik yang tepat digunakan untuk memuat informasi rata-rata biaya bulanan berdasarkan metode pembayarannya adalah barchart. Karena barchart adalah jenis grafik yang paling efektif untuk membandingkan nilai agregat antar kategori diskrit, mengingat kolom metode pembayaran terdiri dari data yang bersifat kategorikal. Berikut merupakan Barchart rata rata dan median biaya bulanan (Monthly Charge) berdasarkan Metode Pembayaran (Payment Method)
Rata-rata biaya bulanan (MonthlyCharges) tertinggi terdapat pada metode pembayaran Electronic check yaitu sebesar $76.26, diikuti oleh:
- Bank transfer (automatic): $67.19
- Credit card (automatic): $66.51
- Mailed check: $43.92 ← Terendah
Terdapat kesenjangan cukup signifikan antara Electronic check dan Mailed check sebesar $32.34, menunjukkan bahwa metode pembayaran berkorelasi dengan tingkat kualitas layanan langganan yang dipilih oleh pelanggan.
Electronic check menjadi metode yang digunakan oleh pelanggan dengan layanan mahal atau paket premium.
- Kemungkinan karena pelanggan ini menggunakan banyak fitur tambahan (phone service, streaming tv, streaming movies dll).
Bank transfer (automatic) dan Credit card (automatic) menunjukkan rata-rata biaya yang stabil dan berada di tengah, mengindikasikan bahwa:
- Pelanggan cenderung berkomitmen jangka panjang.
- Lebih nyaman dengan pembayaran otomatis dan berulang.
Mailed check memiliki rata-rata biaya terendah.
- Menunjukkan kelompok pelanggan dengan paket layanan minimal.
- Bisa juga berasal dari latar belakang yang kurang familiar dengan teknologi atau dari wilayah dengan akses digital yang terbatas.
Perbedaan pola biaya bulanan antar metode pembayaran mencerminkan variasi perilaku pelanggan. Sehingga perlu Analisis distribusi untuk mengetahui kecenderungan/karakteristik pengguna di setiap kategori pembayaran. Dimana membagi pelanggan ke dalam segmen tertentu adalah salah satu pendekatan utama untuk mengenali karakteristik pelanggan, sehingga kita bisa lakukan analisa IQR untuk memberikan dasar yang kuat dan terukur untuk membentuk segmen pelanggan ini.
Interquartile Range (IQR) dan Batas Outlier per Metode Pembayaran¶
IQR (Interquartile Range) menggambarkan sebaran nilai MonthlyCharges di tengah 50% data pelanggan (antara Q1 dan Q3). Nilai ini penting untuk mengetahui keragaman biaya bulanan yang dianggap "normal" serta mendeteksi potensi outlier. Adapun Visualisasi Boxplot sesuai untuk menggambarkan nilai ini
Bank transfer (automatic)
- Q1: $41.09
- Q3: $92.96
- IQR: $51.88
- Lower Bound: −$36.72
- Upper Bound: $170.78
Credit card (automatic)
- Q1: $38.66
- Q3: $90.85
- IQR: $52.19
- Lower Bound: −$39.62
- Upper Bound: $169.13
Electronic check
- Q1: $60.15
- Q3: $94.70
- IQR: $34.55
- Lower Bound: $8.33
- Upper Bound: $146.53
Mailed check
- Q1: $20.15
- Q3: $64.91
- IQR: $44.76
- Lower Bound: −$46.99
- Upper Bound: $132.06
Tidak satu pun metode pembayaran memiliki nilai MonthlyCharges yang berada di luar batas bawah atau atas berdasarkan metode IQR.
Ini menunjukkan bahwa meskipun beberapa metode (seperti Electronic check) memiliki rentang biaya tinggi, semua nilai masih dianggap wajar menurut penyebaran data tengahnya.
Nilai IQR tertinggi dimiliki oleh:
- Credit card (automatic): $52.19
- Bank transfer (automatic): $51.88
- Hal ini menunjukkan bahwa metode otomatis memiliki keragaman biaya terbesar di tengah kelompok pelanggan.
Electronic check memiliki IQR lebih kecil ($34.55) namun tetap menunjukkan distribusi yang terfokus pada segmen biaya tinggi.
Implikasi Bisnis Berdasarkan IQR dan Distribusi Biaya Bulanan¶
Dengan memahami IQR dan distribusi MonthlyCharges per metode pembayaran, perusahaan dapat menyusun strategi diferensiasi yang lebih tajam berdasarkan profil ekonomi pelanggan. Berikut implikasi bisnis yang dapat diambil:
1. Segmen “Kelas Atas / Mytic Glory” : Electronic Check¶
- Q1 dan Q3 berada di kisaran tinggi ($60.15 – $94.70), menunjukkan pelanggan dengan layanan berbiaya premium secara konsisten.
- Rentang IQR sempit, menandakan homogenitas atau keseragaman dalam pengeluaran.
- Implikasi:
- Target untuk bundling layanan eksklusif.
- Potensi untuk program VIP loyalty atau personalisasi layanan tambahan.
2. Segmen “Stabil Beragam” : Autodebet (Bank Transfer & Credit Card)¶
- Q1 relatif rendah (sekitar $38–$41), namun Q3 cukup tinggi (di atas $90).
- IQR besar ($51–$52) menunjukkan variasi layanan luas dari basic hingga premium.
- Metode ini mencerminkan preferensi pembayaran yang otomatis dan nyaman.
- Implikasi:
- Cocok untuk penawaran fleksibel seperti upgrade bertahap (freemium ke premium).
- Pelanggan cenderung loyal dan minim churn, bisa menjadi fokus upselling.
- Promosi berbasis efisiensi dan kemudahan pembayaran dapat efektif.
3. Segmen “Kelas Epic / Konservatif” : Mailed Check¶
- Q1 sangat rendah ($20.15) dan Q3 hanya sampai $64.91.
- Menunjukkan pelanggan yang membayar layanan minimum.
- IQR sedang ($44.76), tetapi tetap di rentang harga rendah.
- Implikasi:
- Pelanggan kemungkinan dari wilayah non-digital atau kurang literasi teknologi.
- Perlu pendekatan edukatif dan promosi migrasi ke metode digital.
- Tidak cocok untuk upsell agresif, tapi bisa ditawarkan paket hemat atau add-on ringan.
Implikasi Bisnis untuk Pihak Bank¶
Analisis distribusi MonthlyCharges dan metode pembayaran tidak hanya bermanfaat bagi penyedia layanan utama (telco), tetapi juga sangat strategis bagi pihak bank sebagai penyedia infrastruktur pembayaran digital.
Strategi Layanan Keuangan yang Tepat Sasaran¶
- Nasabah dengan pengeluaran tinggi dan metode autodebet cocok ditawarkan:
- Kartu kredit premium
- Produk cicilan atau pinjaman personal
- Nasabah pengguna mailed check cocok untuk:
- Rekening tanpa biaya admin
- Literasi digital banking
- Aktivasi mobile banking
Peningkatan Fee Based Income melalui Kolaborasi¶
- Bank bisa bermitra dengan perusahaan layanan untuk:
- Memberikan diskon khusus bagi pelanggan autodebet (contoh: $1–$5/bulan)
- Membuat produk bundling “Bayar otomatis via Bank X”
- Meningkatkan volume transaksi berulang melalui sistem autodebet
Soal 2¶
Jika masa berlangganan (tenure) dikelompokkan menjadi sebagai berikut:
- low: User dengan tenure < 21 bulan
- medium: User dengan tenure 21 - 40 bulan
- high: User dengan tenure > 40 bulan
Buatlah grafik yang menunjukkan jumlah pelanggan berdasarkan pengelompokkan tersebut! (10 poin)
Langkah-langkah
- Kelompokkan data dengan menggunakan fungsi numpy.where atau pandas.cut
- Gunakan fungsi agregasi yang tepat untuk kasus ini.
- Kemudian, buatlah grafik yang tepat menurutmu
Untuk mempermudah kamu, yuk lihat resource di bawah ini:
- Topic Data Processing: Materi DataFrame Aggregation - bagian Groupby
- Topic Python Programming: Materi Conditional Statement - bagian np.where()
agung.head()
| customerID | gender | SeniorCitizen | Partner | Dependents | tenure | PhoneService | MultipleLines | InternetService | OnlineSecurity | ... | DeviceProtection | TechSupport | StreamingTV | StreamingMovies | Contract | PaperlessBilling | PaymentMethod | MonthlyCharges | TotalCharges | Churn | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 7590-VHVEG | Female | 0 | Yes | No | 1 | No | No phone service | DSL | No | ... | No | No | No | No | Month-to-month | Yes | Electronic check | 29.85 | 29.85 | No |
| 1 | 5575-GNVDE | Male | 0 | No | No | 34 | Yes | No | DSL | Yes | ... | Yes | No | No | No | One year | No | Mailed check | 56.95 | 1889.5 | No |
| 2 | 3668-QPYBK | Male | 0 | No | No | 2 | Yes | No | DSL | Yes | ... | No | No | No | No | Month-to-month | Yes | Mailed check | 53.85 | 108.15 | Yes |
| 3 | 7795-CFOCW | Male | 0 | No | No | 45 | No | No phone service | DSL | Yes | ... | Yes | Yes | No | No | One year | No | Bank transfer (automatic) | 42.30 | 1840.75 | No |
| 4 | 9237-HQITU | Female | 0 | No | No | 2 | Yes | No | Fiber optic | No | ... | No | No | No | No | Month-to-month | Yes | Electronic check | 70.70 | 151.65 | Yes |
5 rows × 21 columns
agung.columns
Index(['customerID', 'gender', 'SeniorCitizen', 'Partner', 'Dependents',
'tenure', 'PhoneService', 'MultipleLines', 'InternetService',
'OnlineSecurity', 'OnlineBackup', 'DeviceProtection', 'TechSupport',
'StreamingTV', 'StreamingMovies', 'Contract', 'PaperlessBilling',
'PaymentMethod', 'MonthlyCharges', 'TotalCharges', 'Churn'],
dtype='object')
agung['tenure'].describe()
count 7043.000000 mean 32.371149 std 24.559481 min 0.000000 25% 9.000000 50% 29.000000 75% 55.000000 max 72.000000 Name: tenure, dtype: float64
agung_tenure = agung.copy()
agung_tenure['TenureGroup'] = pd.cut(agung_tenure['tenure'], bins=[0, 20, 40, agung_tenure['tenure'].max()], labels=['Low', 'medium', 'high'], right=True, include_lowest=True)
tenure_counts = agung_tenure['TenureGroup'].value_counts().sort_index()
#jumlah pelanggan per kelompok tenure
tenure_counts
TenureGroup Low 2878 medium 1408 high 2757 Name: count, dtype: int64
plt.figure(figsize=(7, 6))
sns.barplot(x=tenure_counts.index, y=tenure_counts.values)
plt.title('Jumlah Pelanggan Berdasarkan Kelompok Tenure', fontsize=14, fontweight='bold')
plt.xlabel('Kelompok Tenure', fontsize=10)
plt.ylabel('Jumlah Pelanggan', fontsize=10)
plt.tight_layout()
plt.show()
plt.figure(figsize=(7, 6))
ax = sns.barplot(x=tenure_counts.index, y=tenure_counts.values, hue=tenure_counts.index, palette='viridis')
for i, value in enumerate(tenure_counts.values):
ax.text(i, value - 500 , str(value), ha='center', va='bottom', fontsize=12, fontweight='bold')
plt.title('Jumlah Pelanggan Berdasarkan Kelompok Tenure', fontsize=14, fontweight='bold')
plt.xlabel('Kelompok Tenure', fontsize=11)
plt.ylabel('Jumlah Pelanggan', fontsize=11)
plt.tight_layout()
plt.show()
Alasan Penggunaan Barchart¶
Grafik yang digunakan untuk memvisualisasikan jumlah pelanggan berdasarkan kelompok tenure adalah bar chart, karena Bar chart sangat sesuai untuk menampilkan data diskrit atau kategori seperti low, medium, dan high tenure, sehingga memudahkan perbandingan antar kelompok.
Grafik bar di atas menunjukkan distribusi jumlah pelanggan berdasarkan masa berlangganan (tenure) yang telah dikelompokkan menjadi tiga kategori:
Low tenure: pelanggan dengan lama berlangganan kurang dari 21 bulan.
Medium tenure: pelanggan dengan lama berlangganan antara 21 hingga 40 bulan.
High tenure: pelanggan dengan lama berlangganan lebih dari 40 bulan.
Hasil Temuan
Jumlah pelanggan terbanyak berasal dari kelompok Low tenure sebanyak 2.878 pelanggan, diikuti oleh:
High tenure sebanyak 2.757 pelanggan, dan
Medium tenure sebanyak 1.408 pelanggan.
interpretasi¶
Jumlah pelanggan Low yang cukup tinggi menunjukkan bahwa terdapat banyak pelanggan baru atau yang belum lama menggunakan layanan. Hal ini bisa menandakan keberhasilan dalam mendapatkan pelanggan baru.
Kelompok High tenure juga tinggi, yang berarti terdapat basis pelanggan loyal yang sudah lama menggunakan layanan. Ini bisa menjadi target utama untuk program retensi, loyalty rewards, dan penawaran eksklusif.
Kelompok Medium tenure justru paling sedikit, yang mengindikasikan kehilangan pelanggan di fase transisi dari pelanggan baru menjadi pelanggan tetap. Ini bisa menjadi alasan penting untuk mengevaluasi pengalaman pelanggan di bulan bulan pertengahan.
Soal 3¶
Apakah warga senior banyak yang menggunakan layanan telepon? (10 poin) Langkah-langkah:
- Kelompokkan data berdasarkan dengan apakah pelanggan tersebut merupakan warga senior dan menggunakan layanan telepon, kemudian hitung jumlah unik pelanggannya.
- Buat pula rasio persentase dari jumlah unik customer tersebut.
- Untuk mempermudah, buat grafik dengan data tersebut menggunakan libraryseaborn dan gunakan parameter ‘hue’
Untuk mempermudah kamu, yuk lihat resource di bawah ini:
- Topic Data Processing: Materi DataFrame Aggregation - bagian multi Groupby
- Topic Data Visualization: Materi Advanced Plot - bagian Multi Barplot
agung.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 7043 entries, 0 to 7042 Data columns (total 21 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 customerID 7043 non-null object 1 gender 7043 non-null object 2 SeniorCitizen 7043 non-null int64 3 Partner 7043 non-null object 4 Dependents 7043 non-null object 5 tenure 7043 non-null int64 6 PhoneService 7043 non-null object 7 MultipleLines 7043 non-null object 8 InternetService 7043 non-null object 9 OnlineSecurity 7043 non-null object 10 OnlineBackup 7043 non-null object 11 DeviceProtection 7043 non-null object 12 TechSupport 7043 non-null object 13 StreamingTV 7043 non-null object 14 StreamingMovies 7043 non-null object 15 Contract 7043 non-null object 16 PaperlessBilling 7043 non-null object 17 PaymentMethod 7043 non-null object 18 MonthlyCharges 7043 non-null float64 19 TotalCharges 7043 non-null object 20 Churn 7043 non-null object dtypes: float64(1), int64(2), object(18) memory usage: 1.1+ MB
agung['SeniorCitizen'].unique()
array([0, 1])
agung['PhoneService'].unique()
array(['No', 'Yes'], dtype=object)
agung_mentah = agung.groupby(['SeniorCitizen', 'PhoneService'])['customerID'].nunique().reset_index(name='JumlahCustomerYangSeniorplusLanggananTelepon')
semua_customer = agung.groupby(['SeniorCitizen'])['customerID'].nunique().reset_index(name='SemuaCustomer')
hasil_rasio = agung_mentah.merge(semua_customer, on='SeniorCitizen', how='inner')
hasil_rasio['Persentase'] = round(100 * hasil_rasio['JumlahCustomerYangSeniorplusLanggananTelepon'] / hasil_rasio['SemuaCustomer'],2)
hasil_rasio
| SeniorCitizen | PhoneService | JumlahCustomerYangSeniorplusLanggananTelepon | SemuaCustomer | Persentase | |
|---|---|---|---|---|---|
| 0 | 0 | No | 578 | 5901 | 9.79 |
| 1 | 0 | Yes | 5323 | 5901 | 90.21 |
| 2 | 1 | No | 104 | 1142 | 9.11 |
| 3 | 1 | Yes | 1038 | 1142 | 90.89 |
#visualisasi hasil_rasio
plt.figure(figsize=(6, 7))
sns.barplot(x='SeniorCitizen', y='Persentase', hue='PhoneService', data=hasil_rasio)
plt.title('Persentase Pelanggan Senior Citizen yang Berlangganan Layanan Telepon', fontweight='bold')
plt.xlabel('Senior Citizen')
plt.ylabel('Persentase (%)')
plt.xticks(rotation=0)
#kustom lokasi legends
plt.legend(title='Phone Service', loc='upper left')
plt.show()
#visualisasi hasil_rasio
plt.figure(figsize=(5, 6))
sns.barplot(x='SeniorCitizen', y='Persentase', hue='PhoneService', data=hasil_rasio, palette='viridis')
plt.title('Persentase Pelanggan Senior Citizen yang Berlangganan Layanan Telepon', fontsize=10, fontweight='bold')
plt.xlabel('Senior Citizen', fontsize=9)
plt.ylabel('Persentase (%)', fontsize=9)
plt.xticks(rotation=0)
#kustom lokasi legends dan judul
plt.legend(title='Phone Service', fontsize=8, loc='upper right', bbox_to_anchor=(1.3, 1), title_fontsize=9)
plt.show()
Interpretasi¶
Grafik barchart yang ditampilkan menunjukkan persentase pelanggan yang menggunakan layanan telepon (PhoneService) berdasarkan status kewargaan senior (SeniorCitizen). Nilai 0 berarti bukan warga senior / non senior, dan 1 berarti warga senior.
- Mayoritas pelanggan, baik warga senior (1) maupun non senior (0), menggunakan layanan telepon.
- Lebih dari 90% pelanggan dari kedua kelompok (senior dan non senior) menggunakan layanan ini.
- Hanya sekitar 9–10% yang tidak menggunakan layanan telepon, dan jumlah ini relatif kecil.
Dari interpretasi tersebut kita dapat menyimpulkan bahwa layanan telepon sudah menjadi kebutuhan yang penting baik dari kelompok senior maupun non senior.
Perusahaan dapat menjadikan layanan telepon sebagai pilar utama dalam mempertahankan pelanggan, serta sebagai titik masuk untuk cross selling layanan tambahan (seperti internet, TV, atau mobile).
Soal 4¶
Bagaimana distribusi dari total biaya? (10 poin) Langkah-langkah:
- Ubah tipe data kolom total biaya dari object menjadi integer atau float dan perhatikan kelengkapan value dari kolom tersebut.
- Buat plot distribusi untuk kolom tersebut
Untuk mempermudah kamu, yuk lihat resource di bawah ini:
- Topic Data Processing: Materi Transformasi pada Dataframe - bagian Mengubah Tipe Data
agung.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 7043 entries, 0 to 7042 Data columns (total 21 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 customerID 7043 non-null object 1 gender 7043 non-null object 2 SeniorCitizen 7043 non-null int64 3 Partner 7043 non-null object 4 Dependents 7043 non-null object 5 tenure 7043 non-null int64 6 PhoneService 7043 non-null object 7 MultipleLines 7043 non-null object 8 InternetService 7043 non-null object 9 OnlineSecurity 7043 non-null object 10 OnlineBackup 7043 non-null object 11 DeviceProtection 7043 non-null object 12 TechSupport 7043 non-null object 13 StreamingTV 7043 non-null object 14 StreamingMovies 7043 non-null object 15 Contract 7043 non-null object 16 PaperlessBilling 7043 non-null object 17 PaymentMethod 7043 non-null object 18 MonthlyCharges 7043 non-null float64 19 TotalCharges 7043 non-null object 20 Churn 7043 non-null object dtypes: float64(1), int64(2), object(18) memory usage: 1.1+ MB
agung['TotalCharges'].describe()
count 7043 unique 6531 top 20.2 freq 11 Name: TotalCharges, dtype: object
agung['TotalCharges'].head()
0 29.85 1 1889.5 2 108.15 3 1840.75 4 151.65 Name: TotalCharges, dtype: object
agung['TotalCharges'] = pd.to_numeric(agung['TotalCharges'], errors='coerce')
print(agung['TotalCharges'].isnull().sum())
11
agung.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 7043 entries, 0 to 7042 Data columns (total 21 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 customerID 7043 non-null object 1 gender 7043 non-null object 2 SeniorCitizen 7043 non-null int64 3 Partner 7043 non-null object 4 Dependents 7043 non-null object 5 tenure 7043 non-null int64 6 PhoneService 7043 non-null object 7 MultipleLines 7043 non-null object 8 InternetService 7043 non-null object 9 OnlineSecurity 7043 non-null object 10 OnlineBackup 7043 non-null object 11 DeviceProtection 7043 non-null object 12 TechSupport 7043 non-null object 13 StreamingTV 7043 non-null object 14 StreamingMovies 7043 non-null object 15 Contract 7043 non-null object 16 PaperlessBilling 7043 non-null object 17 PaymentMethod 7043 non-null object 18 MonthlyCharges 7043 non-null float64 19 TotalCharges 7032 non-null float64 20 Churn 7043 non-null object dtypes: float64(2), int64(2), object(17) memory usage: 1.1+ MB
# histogram untuk melihat distribusi TotalCharges
plt.figure(figsize=(10, 6))
sns.histplot(agung['TotalCharges'], bins=50, kde=True)
plt.title('Distribusi Total Charges')
plt.xlabel('Total Charges')
plt.ylabel('Frekuensi')
plt.legend()
plt.tight_layout()
plt.show()
C:\Users\Wahgus\AppData\Local\Temp\ipykernel_19720\1844985906.py:7: UserWarning: No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument. plt.legend()
#grafik kde plot untuk melihat distribusi TotalCharges
plt.figure(figsize=(8, 6))
sns.kdeplot(data=agung, x='TotalCharges', fill=True)
plt.title('Distribusi Total Charges', fontsize=14, fontweight='bold')
plt.xlabel('Total Charges', fontsize=12)
plt.ylabel('Kepadatan', fontsize=12)
plt.tight_layout()
plt.show()
plt.figure(figsize=(8, 6))
sns.kdeplot(data=agung, x='TotalCharges', fill=True)
plt.title('Distribusi Total Charges', fontsize=14, fontweight='bold')
plt.xlabel('Total Charges', fontsize=12)
plt.ylabel('Kepadatan', fontsize=12)
plt.axvline(agung['TotalCharges'].mean(), color='red', linestyle='--', label='Mean')
plt.axvline(agung['TotalCharges'].median(), color='green', linestyle='--', label='Median')
plt.tight_layout()
plt.legend(fontsize=11)
plt.grid(True, alpha=0.3)
plt.show()
#garis q1 q3
q1 = agung['TotalCharges'].quantile(0.25)
q3 = agung['TotalCharges'].quantile(0.75)
plt.figure(figsize=(8, 6))
sns.kdeplot(data=agung, x='TotalCharges', fill=True)
plt.title('Distribusi Total Charges', fontsize=14, fontweight='bold')
plt.xlabel('Total Charges', fontsize=12)
plt.ylabel('Kepadatan', fontsize=12)
plt.axvline(q1, color='blue', linestyle='--', label='Q1')
plt.axvline(q3, color='orange', linestyle='--', label='Q3')
plt.tight_layout()
plt.legend(fontsize=11)
plt.grid(True, alpha=0.3)
plt.show()
#garis q1 q2 q3
plt.figure(figsize=(8, 6))
sns.kdeplot(data=agung, x='TotalCharges', fill=True)
plt.title('Distribusi Total Charges', fontsize=14, fontweight='bold')
plt.xlabel('Total Charges', fontsize=12)
plt.ylabel('Kepadatan', fontsize=12)
plt.axvline(q1, color='blue', linestyle='--', label='Q1')
plt.axvline(q3, color='orange', linestyle='--', label='Q3')
plt.axvspan(q1, q3, color='green', alpha=0.2, label=f'IQR Area (${q1:.0f} - ${q3:.0f})')
plt.tight_layout()
plt.legend(fontsize=11)
plt.grid(True, alpha=0.3)
plt.show()
#mencari upperbound dan lowerbound
iqr = q3 - q1
lower_bound = q1 - 1.5 * iqr
upper_bound = q3 + 1.5 * iqr
print(f"Lower Bound: {lower_bound:.2f}")
print(f"Upper Bound: {upper_bound:.2f}")
Lower Bound: -4688.48 Upper Bound: 8884.67
outliers = agung[agung['TotalCharges'] > upper_bound]
print("Jumlah outlier atas:", len(outliers))
Jumlah outlier atas: 0
#nilai iqr total charge
iqr = q3 - q1
print(f"IQR Total Charges: {iqr}")
IQR Total Charges: 3393.2875000000004
#upper bound dan lower bound
lower_bound = q1 - 1.5 * iqr
upper_bound = q3 + 1.5 * iqr
print(f"Lower Bound: {lower_bound}")
print(f"Upper Bound: {upper_bound}")
Lower Bound: -4688.481250000001 Upper Bound: 8884.66875
#garis upper bound
plt.figure(figsize=(8, 6))
sns.kdeplot(data=agung, x='TotalCharges', fill=True)
plt.title('Distribusi Total Charges', fontsize=14, fontweight='bold')
plt.xlabel('Total Charges', fontsize=12)
plt.ylabel('Kepadatan', fontsize=12)
plt.axvline(q1, color='blue', linestyle='--', label='Q1')
plt.axvline(q3, color='orange', linestyle='--', label='Q3')
plt.axvspan(q1, q3, color='green', alpha=0.2, label=f'IQR Area (${q1:.0f} - ${q3:.0f})')
plt.axvline(upper_bound, color='red', linestyle='--', label='Upper Bound')
plt.tight_layout()
plt.legend(fontsize=11)
plt.grid(True, alpha=0.3)
plt.show()
# arsir gg
plt.figure(figsize=(8, 6))
sns.kdeplot(data=agung, x='TotalCharges', fill=True)
plt.title('Distribusi Total Charges', fontsize=14, fontweight='bold')
plt.xlabel('Total Charges', fontsize=12)
plt.ylabel('Kepadatan', fontsize=12)
plt.axvline(q1, color='blue', linestyle='--', label='Q1')
plt.axvline(q3, color='orange', linestyle='--', label='Q3')
plt.axvspan(q1, q3, color='green', alpha=0.2, label=f'IQR Area (${q1:.0f} - ${q3:.0f})')
plt.axvline(upper_bound, color='red', linestyle='--', label='Upper Bound')
x_max = agung['TotalCharges'].max() + 2000
x_min = agung['TotalCharges'].min() - 2000
plt.xlim(x_min, x_max)
plt.axvspan(upper_bound, x_max, color='red', alpha=0.15, label='Area outlier (> 8885)')
plt.tight_layout()
plt.legend(loc='upper right', bbox_to_anchor=(1.28, 1), fontsize=8)
plt.grid(True, alpha=0.3)
plt.show()
# arsir gg
plt.figure(figsize=(8, 6))
sns.kdeplot(data=agung, x='TotalCharges', fill=True)
plt.title('Pola Biaya Pelanggan: Siapa yang Benar-Benar Menguntungkan?', fontsize=14, fontweight='bold')
plt.xlabel('Total Charges', fontsize=12)
plt.ylabel('Kepadatan', fontsize=12)
plt.axvline(q1, color='blue', linestyle='--', label='Q1')
plt.axvline(q3, color='orange', linestyle='--', label='Q3')
plt.axvspan(q1, q3, color='green', alpha=0.2, label=f'Mainstream (Q1 - Q3)')
plt.axvline(upper_bound, color='red', linestyle='--', label='Upper Bound')
x_max = agung['TotalCharges'].max() + 2000
x_min = agung['TotalCharges'].min() - 2000
plt.xlim(x_min, x_max)
plt.axvspan(upper_bound, q3, color='purple', alpha=0.15, label='VIP Area (> Q3)')
plt.text((q1 + q3)/2, 0.00015, 'Mainstream\n(Q1–Q3)', ha='center', fontsize=11, fontweight='bold', bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
plt.text((q3 + upper_bound)/2, 0.00005, 'VIP\n(> Q3)', ha='center', fontsize=11, fontweight='bold',color='darkred',bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
plt.tight_layout()
plt.legend(loc='upper right', bbox_to_anchor=(1.28, 1), fontsize=8)
plt.grid(True, alpha=0.3)
plt.show()
#box plot
plt.figure(figsize=(4, 6))
sns.boxplot(data=agung, y='TotalCharges')
plt.title('Box Plot Total Charges', fontsize=14, fontweight='bold')
plt.xlabel('Total Charges', fontsize=12)
plt.tight_layout()
plt.show()
interpretasi¶
Berikut merupakan grafik KDE plot yang akan digunakan menampilkan distribusi data, Grafik memperlihatkan distribusi sebaran pengeluaran pelanggan (TotalCharges) dalam bentuk kurva kepadatan (KDE plot):
Garis biru putus-putus menunjukan nilai Q1
Garis Kuning putus-putus menunjukan nilai Q3
Daerah yang diarsir hijau menggambarkan rentang Interquartile Range (IQR), yaitu wilayah antara kuartil pertama (Q1) dan kuartil ketiga (Q3) di mana sebagian besar data pelanggan berada.
Daerah yang diarsir merah menggambarkan area yang yang merupakan outlier
Distribusi TotalCharges condong ke kanan (positively skewed), artinya sebagian besar pelanggan membayar dalam jumlah relatif rendah, namun ada sedikit pelanggan yang membayar sangat tinggi.
Q1 (kuartil bawah) berada di sekitar $401 dan Q3 (kuartil atas) di sekitar $3795, menunjukkan bahwa 50% pelanggan memiliki total biaya antara dua nilai tersebut (dalam rentang IQR).
Upper Bound (Q3 + 1.5×IQR) berada di sekitar $8896. Area di atas ini dianggap outlier.
Hampir seluruh data berada di bawah batas upper bound, artinya tidak banyak pelanggan yang benar-benar ekstrem dalam pengeluaran mereka.
Dilakukan analisa dengan menambahkan garis mean dan median
Garis merah putus-putus menunjukkan nilai rata-rata (mean).
Garis hijau putus-putus menunjukkan nilai median.
Didapatkan bahwa nilai mean sebesar $2283.30 dan nilai median sebesar $1397.47, yang artinya grafik positively skewed.
Terlihat bahwa nilai mean berada di kanan (lebih tinggi) dibandingkan median, padahal:
Sebagian besar data menumpuk di sebelah kiri grafik (sekitar 0–2000).
Di bagian kanan grafik (nilai tinggi), data semakin jarang atau memiliki kepadatan rendah.
Median lebih representatif untuk ukuran pemusatan dalam kasus ini karena tidak terlalu dipengaruhi outlier dimana nilainya dapat dilihat langsung pada grafik ditandai dengan garis hijau yang berada tepat dinilai total charges yang memiliki kepadatan tinggi.
Dari hasil analisis statistik deskriptif di atas, kita tidak hanya mendapatkan gambaran umum tentang sebaran biaya pelanggan, tetapi juga mengungkap adanya pola-pola tersembunyi yang penting untuk strategi bisnis.
Distribusi yang condong ke kanan, dominasi pelanggan dalam rentang IQR, serta keberadaan segmen kecil dengan pengeluaran tinggi membuka peluang untuk melakukan segmentasi pasar yang lebih strategis dan berbasis data.
Inilah yang menjadi dasar untuk menjawab pertanyaan kunci berikut:
Siapa yang benar-benar menguntungkan?¶
Berikut adalah interpretasi dari grafik distribusi Total Charges yang telah dikelompokkan ke dalam dua segmen pasar: Mainstream dan VIP.
Dari visualisasi distribusi TotalCharges, kita dapat mengidentifikasi dua segmen utama pelanggan berdasarkan kuartil:
Mainstream (Q1–Q3) → Biaya pelanggan berada di rentang interquartile range (IQR)
VIP (> Q3) → Pelanggan yang membayar jauh di atas rata-rata
Mainstream = Volume Besar, Stabilitas Tinggi¶
Kelompok ini mendominasi jumlah pelanggan. Meski tiap individu menyumbang pendapatan moderat, secara kolektif mereka adalah tulang punggung bisnis. Strategi:
Jaga kepuasan dengan pelayanan yang efisien dan harga yang kompetitif
Fokus pada retensi dan loyalitas
VIP = Volume Kecil, Potensi Margin Tinggi¶
Pelanggan VIP membayar jauh lebih tinggi dari pelanggan rata-rata. Strategi:
Ciptakan layanan eksklusif atau program loyalti kelas atas
Berikan perhatian personal, seperti layanan prioritas, hadiah loyalitas, atau promo khusus
Peluang Segmentasi & Personalisasi¶
Pendekatan satu ukuran untuk semua tidak lagi efektif. Dengan segmentasi berbasis perilaku biaya, kita bisa menawarkan layanan yang lebih relevan. Strategi:
Kampanye pemasaran tertarget berdasarkan pola pengeluaran
Buat paket khusus untuk masing-masing segmen
Mendorong Pelanggan Mainstream ke VIP¶
Pelajari karakteristik pelanggan VIP: durasi langganan, produk yang dibeli, atau channel favorit mereka. Strategi:
Ciptakan jalur naik kelas dengan edukasi nilai tambah
Tawarkan insentif upsell atau cross-sell secara bertahap
Soal 5¶
Apakah pelanggan yang memiliki biaya bulanan lebih tinggi akan cenderung untuk berhenti berlangganan (churn)? (15 poin)
Langkah-langkah:
- Bandingkan distribusi biaya bulanan antara pelanggan yang berhenti dan tetap berlangganan.
- Untuk mempermudah, buat grafik dengan data tersebut menggunakan library seaborn dan gunakan parameter ‘hue’
Untuk mempermudah kamu, yuk lihat resource di bawah ini:
- Topic Data Visualization: Materi Advanced Plot - bagian Multi Distribution plot
agung['Churn'].describe()
count 7043 unique 2 top No freq 5174 Name: Churn, dtype: object
agung['Churn'].value_counts()
Churn No 5174 Yes 1869 Name: count, dtype: int64
agung.groupby('Churn')['MonthlyCharges'].describe()
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| Churn | ||||||||
| No | 5174.0 | 61.265124 | 31.092648 | 18.25 | 25.10 | 64.425 | 88.4 | 118.75 |
| Yes | 1869.0 | 74.441332 | 24.666053 | 18.85 | 56.15 | 79.650 | 94.2 | 118.35 |
#histogram
plt.figure(figsize=(10, 6))
sns.histplot(data=agung, x='MonthlyCharges', hue='Churn', multiple='stack', kde=True, bins=30)
plt.title('Distribusi Monthly Charges berdasarkan Churn', fontsize=14, fontweight='bold')
plt.xlabel('Monthly Charges', fontsize=12)
plt.ylabel('Frekuensi', fontsize=12)
plt.legend(title='Churn', loc='upper right')
plt.tight_layout()
plt.show()
C:\Users\Wahgus\AppData\Local\Temp\ipykernel_19720\651463370.py:7: UserWarning: No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument. plt.legend(title='Churn', loc='upper right')
#kdeplot khusus churn
plt.figure(figsize=(10, 6))
sns.kdeplot(data=agung[agung['Churn'] == 'Yes'], x='MonthlyCharges', fill=True, label='Churn = Yes', color='red')
sns.kdeplot(data=agung[agung['Churn'] == 'No'], x='MonthlyCharges', fill=True, label='Churn = No', color='blue')
plt.title('Distribusi Monthly Charges berdasarkan Churn', fontsize=14, fontweight='bold')
plt.xlabel('Monthly Charges', fontsize=12)
plt.ylabel('Kepadatan', fontsize=12)
plt.legend(title='Churn', loc='upper right')
plt.tight_layout()
plt.show()
# kde plot churn garis mean median
plt.figure(figsize=(10, 6))
sns.kdeplot(data=agung[agung['Churn'] == 'Yes'], x='MonthlyCharges', fill=True, label='Churn = Yes', color='red')
sns.kdeplot(data=agung[agung['Churn'] == 'No'], x='MonthlyCharges', fill=True, label='Churn = No', color='blue')
plt.axvline(agung[agung['Churn'] == 'Yes']['MonthlyCharges'].mean(), color='green', linestyle='--', label='Mean Churn = Yes')
plt.axvline(agung[agung['Churn'] == 'Yes']['MonthlyCharges'].median(), color='orange', linestyle='--', label='Median Churn = Yes')
plt.axvline(agung[agung['Churn'] == 'No']['MonthlyCharges'].mean(), color='red', linestyle='--', label='Mean Churn = No')
plt.axvline(agung[agung['Churn'] == 'No']['MonthlyCharges'].median(), color='blue', linestyle='--', label='Median Churn = No')
plt.title('Distribusi Monthly Charges berdasarkan Churn', fontsize=14, fontweight='bold')
plt.xlabel('Monthly Charges', fontsize=12)
plt.ylabel('Kepadatan', fontsize=12)
plt.legend(title='Churn', loc='upper right')
plt.tight_layout()
plt.grid(True, alpha=0.3)
plt.show()
#nilai iqr
q1_churn_yes = agung[agung['Churn'] == 'Yes']['MonthlyCharges'].quantile(0.25)
q3_churn_yes = agung[agung['Churn'] == 'Yes']['MonthlyCharges'].quantile(0.75)
iqr_churn_yes = q3_churn_yes - q1_churn_yes
q1_churn_no = agung[agung['Churn'] == 'No']['MonthlyCharges'].quantile(0.25)
q3_churn_no = agung[agung['Churn'] == 'No']['MonthlyCharges'].quantile(0.75)
iqr_churn_no = q3_churn_no - q1_churn_no
print(f"IQR Churn = Yes: {iqr_churn_yes}")
print(f"IQR Churn = No: {iqr_churn_no}")
IQR Churn = Yes: 38.050000000000004 IQR Churn = No: 63.300000000000004
# upper bound dan lower bound
lower_bound_churn_yes = q1_churn_yes - 1.5 * iqr_churn_yes
upper_bound_churn_yes = q3_churn_yes + 1.5 * iqr_churn_yes
lower_bound_churn_no = q1_churn_no - 1.5 * iqr_churn_no
upper_bound_churn_no = q3_churn_no + 1.5 * iqr_churn_no
print(f"Lower Bound Churn = Yes: {lower_bound_churn_yes}")
print(f"Upper Bound Churn = Yes: {upper_bound_churn_yes}")
print(f"Lower Bound Churn = No: {lower_bound_churn_no}")
print(f"Upper Bound Churn = No: {upper_bound_churn_no}")
Lower Bound Churn = Yes: -0.9250000000000043 Upper Bound Churn = Yes: 151.275 Lower Bound Churn = No: -69.85 Upper Bound Churn = No: 183.35000000000002
fig, axes = plt.subplots(1, 2, figsize=(12, 6), sharey=True)
sns.kdeplot(data=agung[agung['Churn'] == 'Yes'], x='MonthlyCharges', fill=True, ax=axes[0],color='red')
axes[0].set_title('Churn = Yes', fontsize=12, fontweight='bold')
axes[0].set_xlabel('Monthly Charges')
axes[0].set_ylabel('Kepadatan')
sns.kdeplot(data=agung[agung['Churn'] == 'No'],x='MonthlyCharges',fill=True,ax=axes[1],color='blue')
axes[1].set_title('Churn = No', fontsize=12, fontweight='bold')
axes[1].set_xlabel('Monthly Charges')
axes[1].set_ylabel('')
fig.suptitle('Distribusi Monthly Charges Berdasarkan Status Churn', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
fig, axes = plt.subplots(1, 2, figsize=(13, 6), sharey=True)
sns.kdeplot(data=agung[agung['Churn'] == 'Yes'], x='MonthlyCharges', fill=True, ax=axes[0], color='red')
axes[0].set_title('Churn = Yes', fontsize=12, fontweight='bold')
axes[0].set_xlabel('Monthly Charges')
axes[0].set_ylabel('Kepadatan')
sns.kdeplot(data=agung[agung['Churn'] == 'No'], x='MonthlyCharges', fill=True, ax=axes[1], color='blue')
axes[1].set_title('Churn = No', fontsize=12, fontweight='bold')
axes[1].set_xlabel('Monthly Charges')
axes[1].set_ylabel('')
axes[0].axvline(q1_churn_yes, color='blue', linestyle='--', label='Q1')
axes[0].axvline(q3_churn_yes, color='orange', linestyle='--', label='Q3')
axes[0].axvline(upper_bound_churn_yes, color='red', linestyle='--', label='Upper Bound')
axes[1].axvline(q1_churn_no, color='blue', linestyle='--', label='Q1')
axes[1].axvline(q3_churn_no, color='orange', linestyle='--', label='Q3')
axes[1].axvline(upper_bound_churn_no, color='red', linestyle='--', label='Upper Bound')
fig.suptitle('Distribusi Monthly Charges Berdasarkan Status Churn', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.legend(loc='upper right', bbox_to_anchor=(1.30, 1), fontsize=10)
plt.show()
# arsir area iqr dan outlier
fig, axes = plt.subplots(1, 2, figsize=(13, 6), sharey=True)
sns.kdeplot(data=agung[agung['Churn'] == 'Yes'], x='MonthlyCharges', fill=True, ax=axes[0], color='red', alpha=0.7)
axes[0].set_title('Churn = Yes', fontsize=12, fontweight='bold')
axes[0].set_xlabel('Monthly Charges')
axes[0].set_ylabel('Kepadatan')
sns.kdeplot(data=agung[agung['Churn'] == 'No'], x='MonthlyCharges', fill=True, ax=axes[1], color='blue', alpha=0.7)
axes[1].set_title('Churn = No', fontsize=12, fontweight='bold')
axes[1].set_xlabel('Monthly Charges')
axes[1].set_ylabel('')
axes[0].axvline(q1_churn_yes, color='green', linestyle='--', label='Q1')
axes[0].axvline(q3_churn_yes, color='orange', linestyle='--', label='Q3')
axes[0].axvline(upper_bound_churn_yes, color='black', linestyle='--', label='Upper Bound')
axes[1].axvline(q1_churn_no, color='green', linestyle='--', label='Q1')
axes[1].axvline(q3_churn_no, color='orange', linestyle='--', label='Q3')
axes[1].axvline(upper_bound_churn_no, color='black', linestyle='--', label='Upper Bound')
x_max_bulanan = agung['MonthlyCharges'].max() + 80
x_min_bulanan = agung['MonthlyCharges'].min() - 20
axes[0].set_xlim(x_min_bulanan, x_max_bulanan)
axes[1].set_xlim(x_min_bulanan, x_max_bulanan)
axes[0].axvspan(q1_churn_yes, q3_churn_yes, color='green', alpha=0.2, label='IQR Area (Q1-Q3)')
axes[1].axvspan(q1_churn_no, q3_churn_no, color='green', alpha=0.2, label='IQR Area (Q1-Q3)')
axes[0].axvspan(upper_bound_churn_yes, x_max_bulanan, color='red', alpha=0.15, label='Outlier Area (> Upper Bound)')
axes[1].axvspan(upper_bound_churn_no, x_max_bulanan, color='red', alpha=0.15, label='Outlier Area (> Upper Bound)')
fig.suptitle('Distribusi Monthly Charges Berdasarkan Status Churn', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.legend(loc='upper right', bbox_to_anchor=(1.30, 1), fontsize=10)
plt.show()
# gacorr
fig, axes = plt.subplots(1, 2, figsize=(13, 6), sharey=True)
sns.kdeplot(data=agung[agung['Churn'] == 'Yes'], x='MonthlyCharges', fill=True, ax=axes[0], color='red', alpha=0.7)
axes[0].set_title('Churn = Yes', fontsize=12, fontweight='bold')
axes[0].set_xlabel('Monthly Charges')
axes[0].set_ylabel('Kepadatan')
sns.kdeplot(data=agung[agung['Churn'] == 'No'], x='MonthlyCharges', fill=True, ax=axes[1], color='blue', alpha=0.7)
axes[1].set_title('Churn = No', fontsize=12, fontweight='bold')
axes[1].set_xlabel('Monthly Charges')
axes[1].set_ylabel('')
axes[0].axvline(q1_churn_yes, color='green', linestyle='--', label='Q1')
axes[0].axvline(q3_churn_yes, color='orange', linestyle='--', label='Q3')
axes[0].axvline(upper_bound_churn_yes, color='black', linestyle='--', label='Upper Bound')
axes[1].axvline(q1_churn_no, color='green', linestyle='--', label='Q1')
axes[1].axvline(q3_churn_no, color='orange', linestyle='--', label='Q3')
axes[1].axvline(upper_bound_churn_no, color='black', linestyle='--', label='Upper Bound')
x_max_bulanan = agung['MonthlyCharges'].max() + 75
x_min_bulanan = agung['MonthlyCharges'].min() - 20
axes[0].set_xlim(x_min_bulanan, x_max_bulanan)
axes[1].set_xlim(x_min_bulanan, x_max_bulanan)
axes[0].axvspan(q1_churn_yes, q3_churn_yes, color='yellow', alpha=0.2, label='Low Paying Churners (kikir) Area (Q1-Q3)')
axes[1].axvspan(q1_churn_no, q3_churn_no, color='red', alpha=0.2, label='Low Spending Loyal Area (Q1-Q3)')
axes[0].axvspan(upper_bound_churn_yes, q3_churn_yes, color='green', alpha=0.15, label='Critical Risk Area (> Q3)')
axes[1].axvspan(upper_bound_churn_no, q3_churn_no, color='black', alpha=0.15, label='Resilient Budget Area (> Q3)')
axes[0].text(q1_churn_yes + 19, 0.012, 'Low Paying \n Churners (kikir)', ha='center', fontsize=8, fontweight='bold', bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
axes[0].text(q3_churn_yes + 25, 0.017, 'Critical Risk', ha='center', fontsize=8, fontweight='bold', bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
axes[1].text(q1_churn_no + 28, 0.012, 'Low Spending Loyal', ha='center', fontsize=8, fontweight='bold', bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
axes[1].text(q3_churn_no + 30, 0.010, 'Resilient Budget', ha='center', fontsize=8, fontweight='bold', bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
fig.suptitle('Distribusi Monthly Charges Berdasarkan Status Churn', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.legend(loc='upper right', bbox_to_anchor=(1.30, 1), fontsize=10)
plt.show()
# gacorr bet
fig, axes = plt.subplots(1, 2, figsize=(13, 6), sharey=True)
sns.kdeplot(data=agung[agung['Churn'] == 'Yes'], x='MonthlyCharges', fill=True, ax=axes[0], color='red', alpha=0.7)
axes[0].set_title('Churn = Yes', fontsize=12, fontweight='bold')
axes[0].set_xlabel('Monthly Charges')
axes[0].set_ylabel('Kepadatan')
sns.kdeplot(data=agung[agung['Churn'] == 'No'], x='MonthlyCharges', fill=True, ax=axes[1], color='blue', alpha=0.7)
axes[1].set_title('Churn = No', fontsize=12, fontweight='bold')
axes[1].set_xlabel('Monthly Charges')
axes[1].set_ylabel('')
axes[0].axvline(q1_churn_yes, color='green', linestyle='--', label='Q1')
axes[0].axvline(q3_churn_yes, color='orange', linestyle='--', label='Q3')
axes[0].axvline(upper_bound_churn_yes, color='black', linestyle='--', label='Upper Bound')
axes[1].axvline(q1_churn_no, color='green', linestyle='--', label='Q1')
axes[1].axvline(q3_churn_no, color='orange', linestyle='--', label='Q3')
axes[1].axvline(upper_bound_churn_no, color='black', linestyle='--', label='Upper Bound')
x_max_bulanan = agung['MonthlyCharges'].max() + 75
x_min_bulanan = agung['MonthlyCharges'].min() - 20
axes[0].set_xlim(x_min_bulanan, x_max_bulanan)
axes[1].set_xlim(x_min_bulanan, x_max_bulanan)
axes[0].axvspan(q1_churn_yes, q3_churn_yes, color='yellow', alpha=0.2, label='Low Paying Churners (Q1–Q3)')
axes[1].axvspan(q1_churn_no, q3_churn_no, color='red', alpha=0.2, label='Low Spending Loyal (Q1–Q3)')
axes[0].axvspan(upper_bound_churn_yes, q3_churn_yes, color='green', alpha=0.15, label='Critical Risk Area (> Q3)')
axes[1].axvspan(upper_bound_churn_no, q3_churn_no, color='black', alpha=0.15, label='Resilient Budget Area (> Q3)')
axes[0].text(q1_churn_yes + 19, 0.012, 'Low Paying \n Churners (kikir)', ha='center', fontsize=8, fontweight='bold',bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
axes[0].text(q3_churn_yes + 25, 0.017, 'Critical Risk', ha='center', fontsize=8, fontweight='bold',bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
axes[1].text(q1_churn_no + 28, 0.012, 'Low Spending Loyal', ha='center', fontsize=8, fontweight='bold',bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
axes[1].text(q3_churn_no + 30, 0.010, 'Resilient Budget', ha='center', fontsize=8, fontweight='bold',bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
handles_0, labels_0 = axes[0].get_legend_handles_labels()
handles_1, labels_1 = axes[1].get_legend_handles_labels()
all_handles = handles_0 + handles_1
all_labels = labels_0 + labels_1
unique = dict()
for h, l in zip(all_handles, all_labels):
if l not in unique:
unique[l] = h
fig.legend(unique.values(), unique.keys(), loc='upper right', bbox_to_anchor=(1.2, 1), fontsize=10)
fig.suptitle('Distribusi Monthly Charges Berdasarkan Status Churn', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
fig, axes = plt.subplots(1, 2, figsize=(13, 6), sharey=True)
sns.kdeplot(data=agung[agung['Churn'] == 'Yes'], x='MonthlyCharges', fill=True, ax=axes[0], color='#7B1FA2', alpha=0.7)
axes[0].set_title('Churn = Yes', fontsize=12, fontweight='bold')
axes[0].set_xlabel('Monthly Charges')
axes[0].set_ylabel('Kepadatan')
sns.kdeplot(data=agung[agung['Churn'] == 'No'], x='MonthlyCharges', fill=True, ax=axes[1], color='#00796B', alpha=0.7)
axes[1].set_title('Churn = No', fontsize=12, fontweight='bold')
axes[1].set_xlabel('Monthly Charges')
axes[1].set_ylabel('')
axes[0].axvline(q1_churn_yes, color='#388E3C', linestyle='--', label='Q1')
axes[0].axvline(q3_churn_yes, color='#F57C00', linestyle='--', label='Q3')
axes[0].axvline(upper_bound_churn_yes, color='#424242', linestyle='--', label='Upper Bound')
axes[1].axvline(q1_churn_no, color='#388E3C', linestyle='--', label='Q1')
axes[1].axvline(q3_churn_no, color='#F57C00', linestyle='--', label='Q3')
axes[1].axvline(upper_bound_churn_no, color='#424242', linestyle='--', label='Upper Bound')
x_max_bulanan = agung['MonthlyCharges'].max() + 75
x_min_bulanan = agung['MonthlyCharges'].min() - 20
axes[0].set_xlim(x_min_bulanan, x_max_bulanan)
axes[1].set_xlim(x_min_bulanan, x_max_bulanan)
axes[0].axvspan(q1_churn_yes, q3_churn_yes, color='#FFF59D', alpha=0.5, label='Low Paying Churners (Q1–Q3)')
axes[0].axvspan(upper_bound_churn_yes, q3_churn_yes, color='#E1BEE7', alpha=0.4, label='Critical Risk Area (> Q3)')
axes[1].axvspan(q1_churn_no, q3_churn_no, color='#B2DFDB', alpha=0.5, label='Low Spending Loyal (Q1–Q3)')
axes[1].axvspan(upper_bound_churn_no, q3_churn_no, color='#E0E0E0', alpha=0.5, label='Resilient Budget Area (> Q3)')
axes[0].axvspan(x_min_bulanan, q1_churn_yes, color='#FFCDD2', alpha=0.4, label='Im Here for free trial (< Q1)')
axes[1].axvspan(x_min_bulanan, q1_churn_no, color='#C5CAE9', alpha=0.4, label='Promising (< Q1)')
axes[0].text(q1_churn_yes + 19, 0.012, 'Low Paying \nChurners (kikir)\n(Q1–Q3)', ha='center',fontsize=8, fontweight='bold',bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
axes[0].text(q3_churn_yes + 25, 0.017, 'Critical Risk Area\n(> Q3)', ha='center',fontsize=8, fontweight='bold',bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
axes[1].text(q1_churn_no + 28, 0.012, 'Low Spending Loyal\n(Q1–Q3)', ha='center',fontsize=8, fontweight='bold',bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
axes[1].text(q3_churn_no + 30, 0.010, 'Resilient Budget Area\n(> Q3)', ha='center',fontsize=8, fontweight='bold',bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
axes[0].text(q1_churn_yes - 20, 0.005, 'Im Here For\nFree Trial\n(< Q1)', ha='center', fontsize=8, fontweight='bold',bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
axes[1].text(q1_churn_no - 10, 0.005, 'Promising\n(< Q1)', ha='center',fontsize=8, fontweight='bold',bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
handles_0, labels_0 = axes[0].get_legend_handles_labels()
handles_1, labels_1 = axes[1].get_legend_handles_labels()
all_handles = handles_0 + handles_1
all_labels = labels_0 + labels_1
unique = dict()
for h, l in zip(all_handles, all_labels):
if l not in unique:
unique[l] = h
fig.legend(unique.values(), unique.keys(), loc='upper right', bbox_to_anchor=(1.20, 1), fontsize=10)
# Judul keseluruhan
fig.suptitle('Siapa yang Akan Pergi, dan Siapa yang Layak Dipertahankan?', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
#boxplot
plt.figure(figsize=(8, 6))
sns.boxplot(data=agung, x='Churn', y='MonthlyCharges')
plt.title('Box Plot Monthly Charges berdasarkan Churn', fontsize=14, fontweight='bold')
plt.xlabel('Churn', fontsize=12)
plt.ylabel('Monthly Charges', fontsize=12)
plt.xticks(rotation=0)
plt.tight_layout()
plt.show()
from scipy.stats import mannwhitneyu
churn_yes = agung[agung['Churn'] == 'Yes']['MonthlyCharges']
churn_no = agung[agung['Churn'] == 'No']['MonthlyCharges']
# Lakukan uji Mann-Whitney U Test
stat, p = mannwhitneyu(churn_yes, churn_no, alternative='two-sided')
# Tampilkan hasil
print("Statistik U:", stat)
print("p-value:", p)
Statistik U: 6003125.5 p-value: 3.311627651988584e-54
alpha = 0.05
if p < alpha:
print("Hasil: Tolak H0 → Terdapat perbedaan signifikan antara biaya bulanan pelanggan churn dan tidak churn.")
else:
print("Hasil: Gagal tolak H0 → Tidak terdapat perbedaan signifikan antara biaya bulanan pelanggan churn dan tidak churn.")
Hasil: Tolak H0 → Terdapat perbedaan signifikan antara biaya bulanan pelanggan churn dan tidak churn.
Interpretasi¶
Grafik KDE ini membandingkan sebaran biaya bulanan (MonthlyCharges) antara pelanggan yang Churn (Yes) dan Tidak Churn (No), lengkap dengan garis vertikal yang menandai mean (rata-rata) dan median (nilai tengah) untuk masing-masing kelompok:
Churn = Yes
Garis hijau putus-putus: Mean ≈ 74.44
Garis oranye putus-putus: Median ≈ 79.65
Terlihat bahwa median berada di kanan mean, yang merupakan ciri khas distribusi condong ke kiri (negatively skewed). Ini menunjukkan bahwa sebagian besar pelanggan churn berada di biaya menengah ke atas, dengan sedikit pelanggan yang membayar sangat rendah dan menarik nilai mean ke kiri.
Churn = No
Garis merah putus-putus: Mean ≈ 61.27
Garis biru putus-putus: Median ≈ 64.43
Pada kelompok pelanggan yang tidak churn, median sedikit lebih besar dari mean, namun posisinya cukup dekat menunjukkan bahwa distribusi cukup simetris, atau mendekati normal.
Distribusi churn condong ke kiri (negatively skewed) artinya banyak pelanggan churn justru membayar cukup tinggi. dan Distribusi pelanggan tidak churn cenderung lebih seimbang dan normal.
Uji Mann-Whiteney¶
Untuk mendukung visualisasi distribusi MonthlyCharges berdasarkan status Churn, dilakukan Uji Mann–Whitney U. Uji ini digunakan karena:
- Data MonthlyCharges tidak berdistribusi normal, terutama pada kelompok Churn = Yes (skewed).
- Mann–Whitney U adalah uji non-parametrik yang cocok untuk dua sampel independen dengan distribusi tidak normal.
Statistik U: 6,003,125.5
p-value: 3.31 × 10-54
α (alpha): 0.05
Karena p-value < α (0.05), maka keputusan uji adalah Tolak Ho.
Maka :
- Terdapat perbedaan yang signifikan secara statistik antara distribusi MonthlyCharges pelanggan yang churn dan tidak churn. Dengan kata lain, biaya bulanan memengaruhi kemungkinan pelanggan akan berhenti berlangganan atau tetap bertahan.
Hasil ini konsisten dan memperkuat visualisasi KDE yang telah ditampilkan sebelumnya:
Distribusi MonthlyCharges jelas berbeda bentuk antara dua kelompok.
Perbedaan ukuran pemusatan (median) serta bentuk distribusi menunjukkan pola perilaku pelanggan yang tidak sama.
Hasil uji statistik membuktikan bahwa perbedaan tersebut bukan sekadar kebetulan (random variation).
Setelah terbukti secara statistik bahwa terdapat perbedaan signifikan antara MonthlyCharges pelanggan churn dan tidak churn melalui uji Mann–Whitney, langkah selanjutnya adalah:
- Memahami bagaimana perbedaan ini terdistribusi di dalam kelompok masing-masing, serta bagaimana kita bisa mengidentifikasi karakteristik dari pelanggan di tiap rentang.
Kita sudah tahu "ada perbedaan", dan Sekarang kita ingin tahu "perbedaan itu berada di mana dan seperti apa bentuknya?"
IQR¶
Gambar berikut menampilkan distribusi biaya bulanan (MonthlyCharges) berdasarkan status Churn, diperkaya dengan:
Garis Q1 dan Q3 → untuk membentuk IQR (rentang tengah data)
Garis Upper Bound → untuk menentukan area outlier
Arsiran hijau → wilayah utama (IQR), tempat mayoritas pelanggan berada
Arsiran merah muda → wilayah di atas Upper Bound, diasumsikan sebagai pelanggan premium atau ekstrem
Dengan pembagian ini, kita dapat melakukan segmentasi pelanggan berdasarkan karakteristik biaya bulanan mereka. Berikut contohnya:
Churn = Yes
- (< Q1) Eksperimen/Pengguna Trial → Berbiaya rendah, mudah churn
- (Q1 – Q3 (IQR)) Low Paying Churners → Membayar moderat, tapi tidak loyal
- (> Q3) Critical Risk → Membayar mahal, tapi tetap churn → Bencana Besar
Churn = No
- (< Q1) Promising Budget Users → Biaya rendah, tetap loyal
- (Q1 – Q3 (IQR)) Low Spending Loyalists → Stabil, hemat, setia
- (> Q3) Resilient Budget/Premiums → Membayar tinggi dan tetap bertahan → aset bisnis utama / ternak uang (Timothy Ronald)
Plot segmentasi di atas menunjukkan bagaimana dan di mana perbedaan itu terjadi, serta apa arti bisnisnya. Dengan membingkai plot berdasarkan status churn dan segmentasi biaya, grafik ini menjawab satu pertanyaan strategis penting:
"Siapa pelanggan yang akan pergi, dan siapa yang layak dipertahankan?"¶
Visualisasi ini menyajikan pelanggan berdasarkan biaya bulanan (Monthly Charges) dan status churn, yang telah dibagi ke dalam segmen-segmen perilaku berbasis kuartil (Q1, Q3). Hasilnya bukan sekadar distribusi statistik. ini adalah peta risiko dan peluang bisnis.
1. Churn = Yes → Mereka yang Akan Pergi¶
Im Here For Free Trial (< Q1)Segmen ini terdiri dari pelanggan yang membayar sangat rendah. Mereka mungkin hanya mencoba layanan, tidak terlalu terikat, dan mudah hilang. Mereka tidak loyal, dan tidak menghasilkan pendapatan signifikan.
Implikasi: Biarkan churn, atau tawarkan upsell awal bila ada sinyal potensi naik.
Low Paying Churners (Q1–Q3)Pelanggan di area utama namun tetap churn. Mereka membayar cukup tetapi mungkin tidak merasakan nilai tambah.
Implikasi: Sinyal bahaya Segmentasi ini menunjukkan potensi kegagalan layanan utama (seperti phoneservice, internet). lakukan evaluasi kualitas layanan.
Critical Risk Area (> Q3)Pelanggan yang membayar tinggi namun tetap churn. Ini adalah Sinyal paling kritis pelanggan yang kecewa meskipun telah investasi besar.
Implikasi: Segera lakukan intervensi! Perbaiki customer experience, lakukan analisis individual, dan periksa semuanya bisa jadi layanan internet diretas hengker.
2. Churn = No → Mereka yang Bertahan¶
Promising (< Q1)Mereka membayar rendah namun tidak churn. Ini adalah benih loyalitas jangka panjang, yang mungkin akan bertumbuh jika diberikan perhatian.
Implikasi: Bangun engagement secara bertahap. Program onboarding, fitur discovery, dan promosi bertarget bisa membuka potensi mereka.
Low Spending Loyal (Q1–Q3)Ini adalah mayoritas pelanggan. Mereka loyal meski pengeluaran moderat. Merekalah tulang punggung stabilitas bisnis.
Implikasi: Jaga hubungan, sediakan fitur bernilai tinggi tanpa memaksa mereka naik paket. Fokus pada retensi dan kepuasan.
Resilient Budget Area (> Q3)Pelanggan ini membayar tinggi dan tetap loyal. Mereka adalah premium customers yang percaya pada nilai yang ditawarkan.
Implikasi: Pertahankan dengan layanan eksklusif, loyalty program, atau akses prioritas. Mereka adalah segmen yang memberi margin tinggi.
Implikasi Bisnis: Data yang Bicara, Strategi yang Bekerja¶
Dengan membagi pelanggan berdasarkan perilaku aktual mereka (biaya dan churn), kita tidak lagi mengandalkan intuisi atau demografi semata. Kita menyasar langsung pada:
Segmentasi strategis: Mengetahui siapa yang harus dipertahankan, siapa yang dibiarkan pergi, dan siapa yang bisa tumbuh.
Efisiensi retensi: Tidak semua churn harus dicegah. Fokus pada segmen bernilai tinggi.
Personalisasi pemasaran: Setiap segmen butuh pendekatan berbeda.
Didukung oleh hasil statistik (uji Mann Whitney dan distribusi yang berbeda secara signifikan), segmentasi ini bukan asumsi tapi bukti statistik yang berdasar
Soal 6¶
Bagaimana pengaruh memiliki pasangan & tanggungan terhadap tingkat churn pelanggan? (15 poin) Langkah-langkah:
- Gabungkan data yang menunjukan pelanggan memiliki pasangan dan/atau tanggungan.
- Memiliki pasangan dan tanggungan
- Memiliki pasangan dan tidak memiliki tanggungan
- Tidak memiliki pasangan, namun memiliki tanggungan
- Tidak memiliki pasangan dan tanggungan
- Kemudian, hitung rasio pelanggan yang berhenti berlangganan (churn)
Untuk mempermudah kamu, yuk lihat resource di bawah ini:
- Topic Data Processing: Materi DataFrame Aggregation - bagian Groupby
agung.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 7043 entries, 0 to 7042 Data columns (total 21 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 customerID 7043 non-null object 1 gender 7043 non-null object 2 SeniorCitizen 7043 non-null int64 3 Partner 7043 non-null object 4 Dependents 7043 non-null object 5 tenure 7043 non-null int64 6 PhoneService 7043 non-null object 7 MultipleLines 7043 non-null object 8 InternetService 7043 non-null object 9 OnlineSecurity 7043 non-null object 10 OnlineBackup 7043 non-null object 11 DeviceProtection 7043 non-null object 12 TechSupport 7043 non-null object 13 StreamingTV 7043 non-null object 14 StreamingMovies 7043 non-null object 15 Contract 7043 non-null object 16 PaperlessBilling 7043 non-null object 17 PaymentMethod 7043 non-null object 18 MonthlyCharges 7043 non-null float64 19 TotalCharges 7032 non-null float64 20 Churn 7043 non-null object dtypes: float64(2), int64(2), object(17) memory usage: 1.1+ MB
agung['PartnerDependents'] = agung['Partner'].astype(str) + '_' + agung['Dependents'].astype(str)
hasil_partner_dependents = agung['PartnerDependents'].value_counts().reset_index()
hasil_partner_dependents.columns = ['PartnerDependents', 'JumlahPelanggan']
print(hasil_partner_dependents)
rasio_churn_partner_dependents = agung.groupby('PartnerDependents')['Churn'].value_counts(normalize=True).unstack().fillna(0)
print(rasio_churn_partner_dependents)
PartnerDependents JumlahPelanggan 0 No_No 3280 1 Yes_Yes 1749 2 Yes_No 1653 3 No_Yes 361 Churn No Yes PartnerDependents No_No 0.657622 0.342378 No_Yes 0.786704 0.213296 Yes_No 0.745917 0.254083 Yes_Yes 0.857633 0.142367
rasio_churn_partner_dependents = rasio_churn_partner_dependents * 100
print(rasio_churn_partner_dependents)
Churn No Yes PartnerDependents No_No 65.762195 34.237805 No_Yes 78.670360 21.329640 Yes_No 74.591652 25.408348 Yes_Yes 85.763293 14.236707
label_map = {
'No_No': 'Tidak Ada Keduanya',
'No_Yes': 'Tanggungan Saja',
'Yes_No': 'Pasangan Saja',
'Yes_Yes': 'Pasangan & Tanggungan'
}
rasio_churn_partner_dependents.rename(index=label_map, inplace=True)
rasio_churn_partner_dependents.plot(kind='bar',stacked=False,figsize=(10, 6))
plt.title('Rasio Churn dan Tidak Churn berdasarkan Kombinasi Partner & Dependents')
plt.xlabel('Kombinasi Partner & Dependents')
plt.ylabel('Rasio (%)')
plt.xticks(rotation=15)
plt.legend(title='Status Churn')
plt.tight_layout()
plt.show()
rasio_churn_partner_dependents.plot(kind='bar',stacked=False,color=['salmon', 'skyblue'],edgecolor='black',figsize=(10, 6))
plt.title('Rasio Churn dan Tidak Churn berdasarkan Kombinasi Partner & Dependents')
plt.xlabel('Kombinasi Partner & Dependents')
plt.ylabel('Rasio (%)')
plt.xticks(rotation=15)
plt.legend(title='Status Churn')
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()
Interpretasi: Pengaruh Memiliki Pasangan & Tanggungan terhadap Tingkat Churn Pelanggan¶
Berdasarkan analisis kombinasi kolom Partner dan Dependents, serta perhitungan rasio churn, ditemukan bahwa status sosial pelanggan (memiliki pasangan dan/atau tanggungan) memiliki pengaruh signifikan terhadap kecenderungan pelanggan untuk berhenti berlangganan (churn).
Temuan Utama:¶
Pelanggan yang tidak memiliki pasangan maupun tanggungan (“Tidak Ada Keduanya”) memiliki tingkat churn paling tinggi, menunjukkan kerentanan terhadap penghentian layanan.
Sebaliknya, pelanggan yang memiliki pasangan dan tanggungan memiliki tingkat churn paling rendah.
Dua kelompok di antaranya (memiliki hanya pasangan / hanya tanggungan) menunjukkan tingkat churn menengah, tetapi tetap lebih rendah daripada yang tidak punya keduanya.
Pola Umum: Semakin “stabil” kehidupan sosial seorang pelanggan (punya pasangan dan tanggungan), semakin kecil kemungkinan mereka untuk churn.
Kesimpulan¶
Carilah Pasangan dan Jangan Jomblo niscaya rejeki akan selalu ada dan kita tidak akan kehilangan langganan wifi kita
Soal 7¶
Buatlah insight dari data telco customer, dan sertakan storyline pada visualisasi tersebut dengan memanfaatkan atribut tenure, contract, monthly charges, status*, jumlah pelanggan, dan churn! (30 poin)
Langkah-langkah:
- Wajib menggunakan kelima atribut (tenure, contract, monthly charges, status, jumlah pelanggan, churn)
- Cari insight yang berkaitan dengan kelima atribut tersebut, dan visualisasikan (usahakan membuat visualisasi lebih dari satu)!
- Tambahkan storyline di dalam visualisasi tersebut
Untuk mempermudah kamu, yuk lihat resource di bawah ini:
- Topic Data Visualization: Materi Advanced Plot - bagian Pengenalan Data Storytelling
*Atribut status didapatkan dengan asumsi:
- Married : pelanggan adalah senior citizen atau memiliki partner atau memiliki dependents
- Single : tidak memiliki status seperti yang disebutkan diatas
contoh :
Hint:
Untuk memudahkan bagaimana membuat data storytelling, kamu dapat menjawab
beberapa pertanyaan berikut :
Pelanggan banyak melakukan churn,
- Pada saat tenure berapa bulan?
- Term contract jenis apa yang dipilih oleh pelanggan?
- Status pelanggan seperti apa? Bagaimana persebarannya jika dilihat dari tenure pelanggan?
- Berapa monthly charges pelanggan? Apakah status berpengaruh pada monthly charges pelanggan?
Pertanyaan diatas hanya sebagai alat bantu saja, kamu dapat bebas membuat story telling dengan data yang dimiliki
agung['Status'] = agung.apply(lambda row: 'Married' if (row['SeniorCitizen'] == 1 or row['Partner'] == 'Yes' or row['Dependents'] == 'Yes') else 'Single', axis=1)
profil_pelanggan = agung.groupby(['Contract', 'Status', 'Churn']).agg({
'tenure': 'mean',
'MonthlyCharges': 'mean',
'customerID': 'count'
}).reset_index().rename(columns={'customerID': 'JumlahPelanggan', 'tenure': 'RataRataTenure', 'MonthlyCharges': 'RataRataMonthlyCharges'})
# Visualisasi hubungan antara kontrak, status, churn, rata-rata tenure, dan rata-rata biaya bulanan
fig, axes = plt.subplots(2, 1, figsize=(14, 12))
# Plot 1: Rata-rata tenure pelanggan berdasarkan kontrak, status, dan churn
sns.barplot(data=profil_pelanggan, x='Contract', y='RataRataTenure', hue='Churn', palette='coolwarm', ax=axes[0])
axes[0].set_title('Rata-rata Durasi Berlangganan (Tenure) Berdasarkan Jenis Kontrak dan Churn')
axes[0].set_ylabel('Rata-rata Tenure (Bulan)')
axes[0].set_xlabel('Jenis Kontrak')
axes[0].legend(title='Churn')
# Plot 2: Rata-rata biaya bulanan berdasarkan kontrak, status, dan churn
sns.barplot(data=profil_pelanggan, x='Contract', y='RataRataMonthlyCharges', hue='Churn', palette='coolwarm', ax=axes[1])
axes[1].set_title('Rata-rata Biaya Bulanan Berdasarkan Jenis Kontrak dan Churn')
axes[1].set_ylabel('Rata-rata Biaya Bulanan ($)')
axes[1].set_xlabel('Jenis Kontrak')
axes[1].legend(title='Churn')
# Menambahkan judul utama dan storyline
fig.suptitle('Storytelling Profil Pelanggan Telco: Hubungan antara Jenis Kontrak, Status, Durasi Berlangganan, dan Biaya Bulanan terhadap Churn', fontsize=16)
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()
# Visualisasi tambahan yang melibatkan status pelanggan (Married/Single) untuk analisis lebih mendalam
fig, axes = plt.subplots(2, 1, figsize=(14, 12))
# Plot 1: Rata-rata tenure berdasarkan jenis kontrak, status, dan churn
sns.barplot(data=profil_pelanggan, x='Contract', y='RataRataTenure', hue='Status', palette='Set2', ax=axes[0])
axes[0].set_title('Rata-rata Durasi Berlangganan Berdasarkan Jenis Kontrak dan Status Pelanggan')
axes[0].set_ylabel('Rata-rata Tenure (Bulan)')
axes[0].set_xlabel('Jenis Kontrak')
axes[0].legend(title='Status')
# Plot 2: Rata-rata biaya bulanan berdasarkan jenis kontrak, status, dan churn
sns.barplot(data=profil_pelanggan, x='Contract', y='RataRataMonthlyCharges', hue='Status', palette='Set2', ax=axes[1])
axes[1].set_title('Rata-rata Biaya Bulanan Berdasarkan Jenis Kontrak dan Status Pelanggan')
axes[1].set_ylabel('Rata-rata Biaya Bulanan ($)')
axes[1].set_xlabel('Jenis Kontrak')
axes[1].legend(title='Status')
# Menambahkan storyline
fig.suptitle('Analisis Mendalam: Profil Pelanggan Berdasarkan Jenis Kontrak, Status, Tenure, dan Biaya Bulanan', fontsize=16)
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()
# Menyiapkan data pelanggan yang tidak churn untuk analisis faktor yang mempengaruhi pelanggan loyal
no_churn = agung[agung['Churn'] == 'No']
# Plot 1: Distribusi tenure untuk pelanggan yang tidak churn
plt.figure(figsize=(14, 6))
sns.histplot(no_churn['tenure'], bins=30, kde=True)
plt.title('Distribusi Tenure untuk Pelanggan yang Tidak Churn')
plt.xlabel('Tenure (bulan)')
plt.ylabel('Jumlah Pelanggan')
plt.show()
# Plot 2: Perbandingan jenis kontrak untuk pelanggan tidak churn
plt.figure(figsize=(8, 6))
sns.countplot(data=no_churn, x='Contract', palette='Set2')
plt.title('Jenis Kontrak Pelanggan yang Tidak Churn')
plt.xlabel('Jenis Kontrak')
plt.ylabel('Jumlah Pelanggan')
plt.show()
# Plot 3: Status pelanggan yang tidak churn
plt.figure(figsize=(8, 6))
sns.countplot(data=no_churn, x='Status', palette='Set1')
plt.title('Status Pelanggan yang Tidak Churn')
plt.xlabel('Status Pelanggan')
plt.ylabel('Jumlah Pelanggan')
plt.show()
# Plot 4: Distribusi Monthly Charges untuk pelanggan tidak churn
plt.figure(figsize=(14, 6))
sns.histplot(no_churn['MonthlyCharges'], bins=30, kde=True, color='green')
plt.title('Distribusi Biaya Bulanan untuk Pelanggan yang Tidak Churn')
plt.xlabel('Monthly Charges ($)')
plt.ylabel('Jumlah Pelanggan')
plt.show()
C:\Users\Wahgus\AppData\Local\Temp\ipykernel_19720\4052751098.py:14: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.countplot(data=no_churn, x='Contract', palette='Set2')
C:\Users\Wahgus\AppData\Local\Temp\ipykernel_19720\4052751098.py:22: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.countplot(data=no_churn, x='Status', palette='Set1')
# Mengecek apakah ada pelanggan yang memenuhi semua kriteria ideal secara bersamaan
ideal_customer = agung[
(agung['tenure'] > 24) &
(agung['Contract'] == 'Month-to-month') &
(agung['Status'] == 'Married') &
(agung['MonthlyCharges'].between(40, 80))
]
# Menghitung jumlah pelanggan dengan status tersebut berdasarkan churn
status_ideal_customer = ideal_customer['Churn'].value_counts().reset_index()
status_ideal_customer.columns = ['Churn', 'JumlahPelanggan']
status_ideal_customer
| Churn | JumlahPelanggan | |
|---|---|---|
| 0 | No | 175 |
| 1 | Yes | 54 |
# Visualisasi piechart jumlah pelanggan ideal berdasarkan status churn
plt.figure(figsize=(8, 6))
plt.pie(status_ideal_customer['JumlahPelanggan'], labels=status_ideal_customer['Churn'], autopct='%1.1f%%', startangle=140, colors=['#4CAF50', '#FF5722'])
plt.title('Jumlah Pelanggan Ideal berdasarkan Status Churn')
plt.axis('equal') # Equal aspect ratio ensures that pie chart is circular.
plt.tight_layout()
plt.show()
rank2 = agung[
(agung['Contract'] == 'Month-to-month') &
(agung['Status'] == 'Married') &
(agung['tenure'] <= 12) &
(agung['MonthlyCharges'].between(70, 100))
]
rank2 = rank2['Churn'].value_counts().reset_index()
rank2.columns = ['Churn', 'JumlahPelanggan']
rank2
| Churn | JumlahPelanggan | |
|---|---|---|
| 0 | Yes | 259 |
| 1 | No | 107 |
#pie chart
plt.figure(figsize=(8, 6))
plt.pie(rank2['JumlahPelanggan'], labels=rank2['Churn'], autopct='%1.1f%%', startangle=140, colors=['#2196F3', '#FFC107'])
plt.title('Jumlah Pelanggan Berdasarkan Kriteria Rank 2')
plt.axis('equal')
plt.tight_layout()
plt.show()
import matplotlib.pyplot as plt
# Membuat subplots 2x2
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
# Plot 1: Distribusi tenure
sns.histplot(no_churn['tenure'], bins=30, kde=True, ax=axes[0, 0])
axes[0, 0].set_title('Distribusi Tenure untuk Pelanggan yang Tidak Churn')
axes[0, 0].set_xlabel('Tenure (bulan)')
axes[0, 0].set_ylabel('Jumlah Pelanggan')
# Plot 2: Jenis kontrak
sns.countplot(data=no_churn, x='Contract', palette='Set2', ax=axes[0, 1])
axes[0, 1].set_title('Jenis Kontrak Pelanggan yang Tidak Churn')
axes[0, 1].set_xlabel('Jenis Kontrak')
axes[0, 1].set_ylabel('Jumlah Pelanggan')
# Plot 3: Status pelanggan
sns.countplot(data=no_churn, x='Status', palette='Set1', ax=axes[1, 0])
axes[1, 0].set_title('Status Pelanggan yang Tidak Churn')
axes[1, 0].set_xlabel('Status Pelanggan')
axes[1, 0].set_ylabel('Jumlah Pelanggan')
# Plot 4: Distribusi biaya bulanan
sns.histplot(no_churn['MonthlyCharges'], bins=30, kde=True, color='green', ax=axes[1, 1])
axes[1, 1].set_title('Distribusi Biaya Bulanan untuk Pelanggan yang Tidak Churn')
axes[1, 1].set_xlabel('Monthly Charges ($)')
axes[1, 1].set_ylabel('Jumlah Pelanggan')
# Menambahkan judul utama
fig.suptitle('Analisis Pelanggan yang Tidak Churn: Durasi, Kontrak, Status, dan Biaya Bulanan', fontsize=16)
# Mengatur layout agar rapi
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()
C:\Users\Wahgus\AppData\Local\Temp\ipykernel_19720\3627317045.py:13: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.countplot(data=no_churn, x='Contract', palette='Set2', ax=axes[0, 1]) C:\Users\Wahgus\AppData\Local\Temp\ipykernel_19720\3627317045.py:19: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.countplot(data=no_churn, x='Status', palette='Set1', ax=axes[1, 0])
# Plot 1: Distribusi tenure untuk pelanggan yang tidak churn
plt.figure(figsize=(14, 6))
sns.histplot(no_churn['tenure'], bins=30, kde=True)
plt.title('Distribusi Tenure untuk Pelanggan yang Tidak Churn')
plt.xlabel('Tenure (bulan)')
plt.ylabel('Jumlah Pelanggan')
plt.show()
# Plot 2: garis IQR , q1, q3, upper bound dan lower bound untuk plot distribusi tenure pelanggan tidak churn
q1_tenure_no_churn = no_churn['tenure'].quantile(0.25)
q3_tenure_no_churn = no_churn['tenure'].quantile(0.75)
iqr_tenure_no_churn = q3_tenure_no_churn - q1_tenure_no_churn
lower_bound_tenure_no_churn = q1_tenure_no_churn - 1.5 * iqr_tenure_no_churn
upper_bound_tenure_no_churn = q3_tenure_no_churn + 1.5 * iqr_tenure_no_churn
plt.figure(figsize=(14, 6))
sns.histplot(no_churn['tenure'], bins=30, kde=True)
plt.axvline(q1_tenure_no_churn, color='blue', linestyle='--', label='Q1')
plt.axvline(q3_tenure_no_churn, color='orange', linestyle='--', label='Q3')
plt.title('Distribusi Tenure untuk Pelanggan yang Tidak Churn dengan IQR')
plt.xlabel('Tenure (bulan)')
plt.ylabel('Jumlah Pelanggan')
plt.legend()
plt.show()
q1_tenure_no_churn = no_churn['tenure'].quantile(0.25)
q3_tenure_no_churn = no_churn['tenure'].quantile(0.75)
iqr_tenure_no_churn = q3_tenure_no_churn - q1_tenure_no_churn
lower_bound_tenure_no_churn = q1_tenure_no_churn - 1.5 * iqr_tenure_no_churn
upper_bound_tenure_no_churn = q3_tenure_no_churn + 1.5 * iqr_tenure_no_churn
q3_tenure_no_churn
np.float64(61.0)
# Plot distribusi tenure pelanggan tidak churn menggunakan KDE plot dengan garis Q1, Q3, IQR
plt.figure(figsize=(14, 6))
# KDE plot
sns.kdeplot(no_churn['tenure'], fill=True)
# Garis Q1, Q3, Lower Bound dan Upper Bound
plt.axvline(q1_tenure_no_churn, color='blue', linestyle='--', label='Q1')
plt.axvline(q3_tenure_no_churn, color='orange', linestyle='--', label='Q3')
plt.axvspan(q1_tenure_no_churn, q3_tenure_no_churn, color='green', alpha=0.2, label='IQR Area (Q1-Q3)')
# Label dan judul
plt.title('Distribusi Tenure Pelanggan Tidak Churn (KDE) dengan IQR')
plt.xlabel('Tenure (bulan)')
plt.ylabel('Kepadatan')
plt.legend()
plt.show()
plt.figure(figsize=(14, 6))
sns.kdeplot(no_churn['tenure'], fill=True)
plt.axvline(q1_tenure_no_churn, color='blue', linestyle='--', label='Q1')
plt.axvline(q3_tenure_no_churn, color='orange', linestyle='--', label='Q3')
plt.axvspan(q1_tenure_no_churn, q3_tenure_no_churn, color='red', alpha=0.2, label='IQR Area (Q1-Q3)')
x_min_mantap = q1_tenure_no_churn - 20
x_max_mantap = q3_tenure_no_churn + 20
plt.xlim(x_min_mantap, x_max_mantap)
plt.axvspan(q3_tenure_no_churn, x_max_mantap, color='blue', alpha=0.2, label='IQR Area (Q1-Q3)')
plt.axvspan(q1_tenure_no_churn, x_min_mantap, color='yellow', alpha=0.2, label='IQR Area (Q1-Q3)')
plt.text(q1_tenure_no_churn - 10, 0.008, 'Safe\n(<Q1)', ha='center', fontsize=11, fontweight='bold', bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
plt.text(q3_tenure_no_churn + 10 , 0.008, 'Most Loyal\nGroup(> Q3)', ha='center', fontsize=11, fontweight='bold',bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
plt.text(q1_tenure_no_churn + 20, 0.008, 'Low Loyal\n(Q1–Q3)', ha='center', fontsize=11, fontweight='bold',color = 'darkred', bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
plt.title('Distribusi Tenure Pelanggan Tidak Churn (KDE) dengan IQR')
plt.xlabel('Tenure (bulan)')
plt.ylabel('Kepadatan')
plt.legend()
plt.show()
plt.figure(figsize=(8, 6))
sns.countplot(data=no_churn, x='Contract', palette='Set2')
plt.title('Jenis Kontrak Pelanggan yang Tidak Churn')
plt.xlabel('Jenis Kontrak')
plt.ylabel('Jumlah Pelanggan')
ax = plt.gca()
ax.annotate(
'Pelanggan yang tidak churn\npaling banyak pada kontrak bulanan',
xy=(0.4, 2000),
xytext=(1.2, 1900),
arrowprops=dict(facecolor='steelblue', arrowstyle='->'),
ha='center'
)
plt.tight_layout()
plt.show()
C:\Users\Wahgus\AppData\Local\Temp\ipykernel_19720\669239375.py:2: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.countplot(data=no_churn, x='Contract', palette='Set2')
# Plot 3: Status pelanggan yang tidak churn
plt.figure(figsize=(8, 6))
sns.countplot(data=no_churn, x='Status', palette='Set1')
plt.title('Status Pelanggan yang Tidak Churn')
plt.xlabel('Status Pelanggan')
plt.ylabel('Jumlah Pelanggan')
ax = plt.gca()
ax.annotate(
'Pelanggan yang paling banyak tidak churn\nmerupakan pelanggan yang memiliki \nstatus "married"',
xy=(0.4, 3000),
xytext=(1, 2800),
arrowprops=dict(facecolor='steelblue', arrowstyle='->'),
ha='center'
)
plt.show()
C:\Users\Wahgus\AppData\Local\Temp\ipykernel_19720\3295203990.py:3: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.countplot(data=no_churn, x='Status', palette='Set1')
# Plot 4: Distribusi Monthly Charges untuk pelanggan tidak churn
plt.figure(figsize=(14, 6))
sns.histplot(no_churn['MonthlyCharges'], bins=30, kde=True, color='green')
plt.title('Distribusi Biaya Bulanan untuk Pelanggan yang Tidak Churn')
plt.xlabel('Monthly Charges ($)')
plt.ylabel('Jumlah Pelanggan')
plt.show()
plt.figure(figsize=(14, 6))
sns.kdeplot(data=no_churn['MonthlyCharges'], fill=True, color='green')
plt.title('Distribusi Biaya Bulanan untuk Pelanggan yang Tidak Churn (KDE Plot)')
plt.xlabel('Monthly Charges ($)')
plt.ylabel('Kepadatan')
plt.show()
q1_monthly_charges_no_churn = no_churn['MonthlyCharges'].quantile(0.25)
q3_monthly_charges_no_churn = no_churn['MonthlyCharges'].quantile(0.75)
iqr_monthly_charges_no_churn = q3_monthly_charges_no_churn - q1_monthly_charges_no_churn
lower_bound_monthly_charges_no_churn = q1_monthly_charges_no_churn - 1.5 * iqr_monthly_charges_no_churn
upper_bound_monthly_charges_no_churn = q3_monthly_charges_no_churn + 1.5 * iqr_monthly_charges_no_churn
plt.figure(figsize=(14, 6))
sns.kdeplot(data=no_churn['MonthlyCharges'], fill=True, color='green')
plt.title('Distribusi Biaya Bulanan untuk Pelanggan yang Tidak Churn (KDE Plot)')
plt.xlabel('Monthly Charges ($)')
plt.ylabel('Kepadatan')
x_min_3 = q1_monthly_charges_no_churn - 30
x_max_3= q3_monthly_charges_no_churn + 50
plt.xlim(x_min_3, x_max_3)
plt.axvline(q1_monthly_charges_no_churn, color='blue', linestyle='--', label='Q1')
plt.axvline(q3_monthly_charges_no_churn, color='orange', linestyle='--', label='Q3')
plt.axvspan(q1_monthly_charges_no_churn, q3_monthly_charges_no_churn, color='yellow', alpha=0.2, label='IQR Area (Q1-Q3)')
# plt.axvspan(lower_bound_monthly_charges_no_churn, q1_monthly_charges_no_churn, color='red', alpha=0.2, label='Lower Bound Area (< Q1)')
# plt.axvspan(q3_monthly_charges_no_churn, upper_bound_monthly_charges_no_churn, color='blue', alpha=0.2, label='Upper Bound Area (> Q3)')
# plt.axvspan(upper_bound_monthly_charges_no_churn, no_churn['MonthlyCharges'].max(), color='green', alpha=0.2, label='Upper Bound Area (> Q3)')
plt.text(q1_monthly_charges_no_churn + 10, 0.008, 'Safe\n(<Q1)', ha='center', fontsize=11, fontweight='bold', bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
plt.text(q3_monthly_charges_no_churn + 10, 0.008, 'Safe\n(> Q3)', ha='center', fontsize=11, fontweight='bold', bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
plt.text(q1_monthly_charges_no_churn + 20, 0.008, 'Churner\n(Q1–Q3)', ha='center', fontsize=11, fontweight='bold', color='darkred', bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
plt.legend()
plt.show()
q1_monthly_charges_no_churn = no_churn['MonthlyCharges'].quantile(0.25)
q3_monthly_charges_no_churn = no_churn['MonthlyCharges'].quantile(0.75)
iqr_monthly_charges_no_churn = q3_monthly_charges_no_churn - q1_monthly_charges_no_churn
lower_bound_monthly_charges_no_churn = q1_monthly_charges_no_churn - 1.5 * iqr_monthly_charges_no_churn
upper_bound_monthly_charges_no_churn = q3_monthly_charges_no_churn + 1.5 * iqr_monthly_charges_no_churn
q3_monthly_charges_no_churn
np.float64(88.4)
q1_monthly_charges_no_churn = no_churn['MonthlyCharges'].quantile(0.25)
q3_monthly_charges_no_churn = no_churn['MonthlyCharges'].quantile(0.75)
iqr_monthly_charges_no_churn = q3_monthly_charges_no_churn - q1_monthly_charges_no_churn
lower_bound_monthly_charges_no_churn = q1_monthly_charges_no_churn - 1.5 * iqr_monthly_charges_no_churn
upper_bound_monthly_charges_no_churn = q3_monthly_charges_no_churn + 1.5 * iqr_monthly_charges_no_churn
plt.figure(figsize=(14, 6))
sns.kdeplot(data=no_churn['MonthlyCharges'], fill=True, color='green')
plt.title('Distribusi Biaya Bulanan untuk Pelanggan yang Tidak Churn (KDE Plot)')
plt.xlabel('Monthly Charges ($)')
plt.ylabel('Kepadatan')
x_min_3 = q1_monthly_charges_no_churn - 30
x_max_3= q3_monthly_charges_no_churn + 50
plt.xlim(x_min_3, x_max_3)
plt.axvline(q1_monthly_charges_no_churn, color='blue', linestyle='--', label='Q1')
plt.axvline(q3_monthly_charges_no_churn, color='orange', linestyle='--', label='Q3')
plt.axvspan(q1_monthly_charges_no_churn, q3_monthly_charges_no_churn, color='red', alpha=0.2, label='IQR Area (Q1-Q3)')
plt.axvspan(x_min_3, q1_monthly_charges_no_churn, color='blue', alpha=0.2, label='Safe Area (< Q1)')
plt.axvspan(q3_monthly_charges_no_churn, x_max_3, color='yellow', alpha=0.2, label='Safe Area (> Q3)')
# plt.axvspan(upper_bound_monthly_charges_no_churn, no_churn['MonthlyCharges'].max(), color='green', alpha=0.2, label='Upper Bound Area (> Q3)')
plt.text(q1_monthly_charges_no_churn -10, 0.008, 'Highest Amount\n(<Q1)', ha='center', fontsize=11, fontweight='bold', bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
plt.text(q3_monthly_charges_no_churn + 20, 0.008, 'Safe\n(> Q3)', ha='center', fontsize=11, fontweight='bold', bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
plt.text(q1_monthly_charges_no_churn + 30, 0.008, 'Low\n(Q1–Q3)', ha='center', fontsize=11, fontweight='bold', color='darkred', bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
plt.legend()
plt.show()
yes_churn = agung[agung['Churn'] == 'Yes']
plt.figure(figsize=(14, 6))
sns.histplot(yes_churn['tenure'], bins=30, kde=True)
plt.title('Distribusi Tenure untuk Pelanggan yang Churn')
plt.xlabel('Tenure (bulan)')
plt.ylabel('Jumlah Pelanggan')
plt.show()
plt.figure(figsize=(8, 6))
sns.countplot(data=yes_churn, x='Contract', palette='Set2')
plt.title('Jenis Kontrak Pelanggan yang Churn')
plt.xlabel('Jenis Kontrak')
plt.ylabel('Jumlah Pelanggan')
plt.show()
plt.figure(figsize=(8, 6))
sns.countplot(data=yes_churn, x='Status', palette='Set1')
plt.title('Status Pelanggan yang Churn')
plt.xlabel('Status Pelanggan')
plt.ylabel('Jumlah Pelanggan')
plt.show()
plt.figure(figsize=(14, 6))
sns.histplot(yes_churn['MonthlyCharges'], bins=30, kde=True, color='green')
plt.title('Distribusi Biaya Bulanan untuk Pelanggan yang Churn')
plt.xlabel('Monthly Charges ($)')
plt.ylabel('Jumlah Pelanggan')
plt.show()
C:\Users\Wahgus\AppData\Local\Temp\ipykernel_19720\3745456733.py:14: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.countplot(data=yes_churn, x='Contract', palette='Set2')
C:\Users\Wahgus\AppData\Local\Temp\ipykernel_19720\3745456733.py:22: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.countplot(data=yes_churn, x='Status', palette='Set1')
#cari q1_tenure_yes_churn , q3_tenure_yes_churn
q1_tenure_yes_churn = yes_churn['tenure'].quantile(0.25)
q3_tenure_yes_churn = yes_churn['tenure'].quantile(0.75)
iqr_tenure_yes_churn = q3_tenure_yes_churn - q1_tenure_yes_churn
lower_bound_tenure_yes_churn = q1_tenure_yes_churn - 1.5 * iqr_tenure_yes_churn
upper_bound_tenure_yes_churn = q3_tenure_yes_churn + 1.5 * iqr_tenure_yes_churn
plt.figure(figsize=(14, 6))
sns.kdeplot(yes_churn['tenure'], fill=True)
plt.axvline(q1_tenure_yes_churn, color='blue', linestyle='--', label='Q1')
plt.axvline(q3_tenure_yes_churn, color='orange', linestyle='--', label='Q3')
plt.axvspan(q1_tenure_yes_churn, q3_tenure_yes_churn, color='blue', alpha=0.2, label='IQR Area (Q1-Q3)')
x_min_mantap = q1_tenure_yes_churn - 20
x_max_mantap = q3_tenure_yes_churn + 20
plt.xlim(x_min_mantap, x_max_mantap)
plt.axvspan(q3_tenure_yes_churn, x_max_mantap, color='green', alpha=0.2, label='IQR Area (Q1-Q3)')
plt.axvspan(q1_tenure_yes_churn, x_min_mantap, color='red', alpha=0.2, label='IQR Area (Q1-Q3)')
plt.text(q1_tenure_yes_churn - 10, 0.008, 'Trial doang\n(<Q1)', ha='center', fontsize=11, fontweight='bold', bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
plt.text(q3_tenure_yes_churn + 10 , 0.008, 'Loyal\n(> Q3)', ha='center', fontsize=11, fontweight='bold',bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
plt.text(q1_tenure_yes_churn + 20, 0.008, 'Safe\n(Q1–Q3)', ha='center', fontsize=11, fontweight='bold',color = 'darkred', bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
plt.title('Distribusi Tenure Pelanggan Churn (KDE) dengan IQR')
plt.xlabel('Tenure (bulan)')
plt.ylabel('Kepadatan')
plt.legend()
plt.show()
plt.figure(figsize=(8, 6))
sns.countplot(data=yes_churn, x='Contract', palette='Set2')
plt.title('Jenis Kontrak Pelanggan yang Churn')
plt.xlabel('Jenis Kontrak')
plt.ylabel('Jumlah Pelanggan')
ax = plt.gca()
ax.annotate(
'Pelanggan yang churn\npaling banyak pada kontrak bulanan',
xy=(0.4, 1500),
xytext=(1.2, 1400),
arrowprops=dict(facecolor='steelblue', arrowstyle='->'),
ha='center'
)
plt.show()
C:\Users\Wahgus\AppData\Local\Temp\ipykernel_19720\374669270.py:2: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.countplot(data=yes_churn, x='Contract', palette='Set2')
plt.figure(figsize=(8, 6))
sns.countplot(data=yes_churn, x='Status', palette='Set1')
plt.title('Status Pelanggan yang Churn')
plt.xlabel('Status Pelanggan')
plt.ylabel('Jumlah Pelanggan')
ax = plt.gca()
ax.annotate(
'Pelanggan yang paling banyak tidak churn\nmerupakan pelanggan yang memiliki \nstatus "married"',
xy=(0.6, 900),
xytext=(0.03, 920),
arrowprops=dict(facecolor='steelblue', arrowstyle='->'),
ha='center'
)
plt.show()
C:\Users\Wahgus\AppData\Local\Temp\ipykernel_19720\1942244962.py:2: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.countplot(data=yes_churn, x='Status', palette='Set1')
q1_monthly_charges_yes_churn = yes_churn['MonthlyCharges'].quantile(0.25)
q3_monthly_charges_yes_churn = yes_churn['MonthlyCharges'].quantile(0.75)
iqr_monthly_charges_yes_churn = q3_monthly_charges_yes_churn - q1_monthly_charges_yes_churn
lower_bound_monthly_charges_yes_churn = q1_monthly_charges_yes_churn - 1.5 * iqr_monthly_charges_yes_churn
upper_bound_monthly_charges_yes_churn = q3_monthly_charges_yes_churn + 1.5 * iqr_monthly_charges_yes_churn
plt.figure(figsize=(14, 6))
sns.kdeplot(data=yes_churn['MonthlyCharges'], fill=True, color='green')
plt.title('Distribusi Biaya Bulanan untuk Pelanggan yang Tidak Churn (KDE Plot)')
plt.xlabel('Monthly Charges ($)')
plt.ylabel('Kepadatan')
x_min_3 = q1_monthly_charges_yes_churn - 30
x_max_3= q3_monthly_charges_yes_churn + 50
plt.xlim(x_min_3, x_max_3)
plt.axvline(q1_monthly_charges_yes_churn, color='blue', linestyle='--', label='Q1')
plt.axvline(q3_monthly_charges_yes_churn, color='orange', linestyle='--', label='Q3')
plt.axvspan(q1_monthly_charges_yes_churn, q3_monthly_charges_yes_churn, color='red', alpha=0.2, label='IQR Area (Q1-Q3)')
plt.axvspan(x_min_3, q1_monthly_charges_yes_churn, color='blue', alpha=0.2, label='Safe Area (< Q1)')
plt.axvspan(q3_monthly_charges_yes_churn, x_max_3, color='yellow', alpha=0.2, label='Safe Area (> Q3)')
# plt.axvspan(upper_bound_monthly_charges_no_churn, no_churn['MonthlyCharges'].max(), color='green', alpha=0.2, label='Upper Bound Area (> Q3)')
plt.text(q1_monthly_charges_yes_churn -10, 0.008, 'Most Loyal\n(<Q1)', ha='center', fontsize=11, fontweight='bold', bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
plt.text(q3_monthly_charges_yes_churn + 20, 0.008, 'Safe\n(> Q3)', ha='center', fontsize=11, fontweight='bold', bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
plt.text(q1_monthly_charges_yes_churn + 30, 0.008, 'Low Loyal\n(Q1–Q3)', ha='center', fontsize=11, fontweight='bold', color='darkred', bbox=dict(facecolor='white', alpha=0.7, edgecolor='gray'))
plt.legend()
plt.show()
def label_tenure(t):
if t <= 12:
return '≤1 Tahun'
elif t <= 24:
return '1-2 Tahun'
elif t <= 36:
return '2-3 Tahun'
elif t <= 48:
return '3-4 Tahun'
elif t <= 60:
return '4-5 Tahun'
else:
return '>5 Tahun'
agung['TenureGroup'] = agung['tenure'].apply(label_tenure)
# Buat dua subset: churn dan tidak churn
tenure_yes_churn_pertahun = agung[agung['Churn'] == 'Yes']
tenure_no_churn_pertahun = agung[agung['Churn'] == 'No']
# Plot barchart jumlah pelanggan per grup tenure, dipisah churn dan tidak churn
fig, axes = plt.subplots(1, 2, figsize=(14, 6), sharey=True)
sns.countplot(data=tenure_yes_churn_pertahun, x='TenureGroup', order=['≤1 Tahun','1-2 Tahun','2-3 Tahun','3-4 Tahun','4-5 Tahun','>5 Tahun'], ax=axes[0], palette='Reds')
axes[0].set_title('Jumlah Pelanggan Churn per Kelompok Tenure')
axes[0].set_xlabel('Kelompok Tenure')
axes[0].set_ylabel('Jumlah Pelanggan')
axes[0].tick_params(axis='x', rotation=45)
sns.countplot(data=tenure_no_churn_pertahun, x='TenureGroup', order=['≤1 Tahun','1-2 Tahun','2-3 Tahun','3-4 Tahun','4-5 Tahun','>5 Tahun'], ax=axes[1], palette='Greens')
axes[1].set_title('Jumlah Pelanggan Tidak Churn per Kelompok Tenure')
axes[1].set_xlabel('Kelompok Tenure')
axes[1].set_ylabel('')
axes[1].tick_params(axis='x', rotation=45)
plt.tight_layout()
plt.show()
C:\Users\Wahgus\AppData\Local\Temp\ipykernel_19720\3337457223.py:24: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.countplot(data=tenure_yes_churn_pertahun, x='TenureGroup', order=['≤1 Tahun','1-2 Tahun','2-3 Tahun','3-4 Tahun','4-5 Tahun','>5 Tahun'], ax=axes[0], palette='Reds') C:\Users\Wahgus\AppData\Local\Temp\ipykernel_19720\3337457223.py:30: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.countplot(data=tenure_no_churn_pertahun, x='TenureGroup', order=['≤1 Tahun','1-2 Tahun','2-3 Tahun','3-4 Tahun','4-5 Tahun','>5 Tahun'], ax=axes[1], palette='Greens')
def label_monthly_charge(m):
if m <= 40:
return '<$40'
elif m <= 80:
return '$40-80'
else:
return '>$80'
agung['MonthlyGroup'] = agung['MonthlyCharges'].apply(label_monthly_charge)
# Buat dua subset berdasarkan Churn
bulanan_yes_churn = agung[agung['Churn'] == 'Yes']
bulanan_no_churn = agung[agung['Churn'] == 'No']
fig, axes = plt.subplots(1, 2, figsize=(14, 6), sharey=True)
sns.countplot(data=bulanan_yes_churn, x='MonthlyGroup', order=['<$40', '$40-80', '>$80'], ax=axes[0], palette='Reds')
axes[0].set_title('Jumlah Pelanggan Churn per Kelompok Biaya Bulanan')
axes[0].set_xlabel('Kelompok Biaya Bulanan')
axes[0].set_ylabel('Jumlah Pelanggan')
sns.countplot(data=bulanan_no_churn, x='MonthlyGroup', order=['<$40', '$40-80', '>$80'], ax=axes[1], palette='Greens')
axes[1].set_title('Jumlah Pelanggan Tidak Churn per Kelompok Biaya Bulanan')
axes[1].set_xlabel('Kelompok Biaya Bulanan')
axes[1].set_ylabel('')
plt.tight_layout()
plt.show()
C:\Users\Wahgus\AppData\Local\Temp\ipykernel_19720\3677862183.py:18: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.countplot(data=bulanan_yes_churn, x='MonthlyGroup', order=['<$40', '$40-80', '>$80'], ax=axes[0], palette='Reds') C:\Users\Wahgus\AppData\Local\Temp\ipykernel_19720\3677862183.py:23: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.countplot(data=bulanan_no_churn, x='MonthlyGroup', order=['<$40', '$40-80', '>$80'], ax=axes[1], palette='Greens')
agung['Contract'] = agung['Contract'].astype(str)
agung['Churn'] = agung['Churn'].astype(str)
plt.figure(figsize=(10, 6))
sns.countplot(data=agung, x='Contract', hue='Churn', palette='Set2')
plt.title('Perbandingan Jenis Kontrak Pelanggan: Churn vs Tidak Churn')
plt.xlabel('Jenis Kontrak')
plt.ylabel('Jumlah Pelanggan')
# Menambahkan anotasi
plt.annotate(
'Mayoritas pelanggan churn\nkontrak bulanan',
xy=(0.38, 1600),
xytext=(0.85, 1600),
arrowprops=dict(facecolor='steelblue', arrowstyle='->'),
ha='center'
)
plt.annotate(
'Mayoritas pelanggan tidak churn\njuga pada kontrak bulanan',
xy=(0, 2000),
xytext=(0.6, 2100),
arrowprops=dict(facecolor='green', arrowstyle='->'),
ha='center'
)
plt.tight_layout()
plt.show()
agung.groupby(['Contract', 'Churn']).size().reset_index(name='JumlahPelanggan')
| Contract | Churn | JumlahPelanggan | |
|---|---|---|---|
| 0 | Month-to-month | No | 2220 |
| 1 | Month-to-month | Yes | 1655 |
| 2 | One year | No | 1307 |
| 3 | One year | Yes | 166 |
| 4 | Two year | No | 1647 |
| 5 | Two year | Yes | 48 |
agung_otw_married = agung.groupby(['Status', 'Churn']).size().reset_index(name='Jumlah')
# Hitung total per Status untuk rasio
total_per_status = agung.groupby('Status').size().reset_index(name='Total')
agung_otw_married = agung_otw_married.merge(total_per_status, on='Status')
agung_otw_married['Rasio'] = agung_otw_married['Jumlah'] / agung_otw_married['Total']
plt.figure(figsize=(8, 6))
sns.barplot(data=agung_otw_married, x='Status', y='Rasio', hue='Churn', palette='deep')
plt.title('Rasio Churn terhadap Status Pelanggan')
plt.xlabel('Status Pelanggan')
plt.ylabel('Rasio Churn')
plt.legend(title='Churn')
plt.tight_layout()
plt.show()
agung_otw_married
| Status | Churn | Jumlah | Total | Rasio | |
|---|---|---|---|---|---|
| 0 | Married | No | 3302 | 4324 | 0.763645 |
| 1 | Married | Yes | 1022 | 4324 | 0.236355 |
| 2 | Single | No | 1872 | 2719 | 0.688488 |
| 3 | Single | Yes | 847 | 2719 | 0.311512 |
Non_ideal_customer = agung[
(agung['Contract'] == 'Month-to-month') &
(agung['Status'] == 'Married') &
(agung['tenure'] < 12) &
(agung['MonthlyCharges'] >= 80) &
(agung['MonthlyCharges'] <= 120)
]
percNon_ideal_customer = Non_ideal_customer['Churn'].value_counts().reset_index()
percNon_ideal_customer.columns = ['Churn', 'JumlahPelanggan']
plt.figure(figsize=(8, 6))
plt.pie(percNon_ideal_customer['JumlahPelanggan'], labels=percNon_ideal_customer['Churn'], autopct='%1.1f%%', startangle=140)
plt.title('Segmen Pelanggan Non-Ideal')
plt.axis('equal')
plt.tight_layout()
plt.legend(title='Status Churn')
plt.show()
ideal_customer = agung[(agung['tenure'] > 24) & (agung['Contract'] == 'Two year') & (agung['Status'] == 'Married') & (agung['MonthlyCharges'].between(40, 80))]
perc_ideal_customer = ideal_customer['Churn'].value_counts().reset_index()
perc_ideal_customer.columns = ['Churn', 'JumlahPelanggan']
plt.figure(figsize=(8, 6))
plt.pie(status_ideal_customer['JumlahPelanggan'], labels=perc_ideal_customer['Churn'], autopct='%1.1f%%', startangle=140)
plt.title('Segmen Pelanggan Ideal')
plt.axis('equal')
plt.tight_layout()
plt.legend(title='Status Churn')
plt.show()
# Buat subplot untuk dua pie chart: pelanggan ideal vs non-ideal
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Pie chart untuk pelanggan ideal
axes[0].pie(
perc_ideal_customer['JumlahPelanggan'],
labels=perc_ideal_customer['Churn'],
autopct='%1.1f%%',
startangle=140,
colors=['darkgreen', 'yellow']
)
axes[0].axis('equal')
axes[0].set_title('Segmen Pelanggan Ideal')
# Pie chart untuk pelanggan non-ideal
axes[1].pie(
percNon_ideal_customer['JumlahPelanggan'],
labels=percNon_ideal_customer['Churn'],
autopct='%1.1f%%',
startangle=140,
colors=['yellow', 'darkgreen']
)
axes[1].axis('equal')
axes[1].set_title('Segmen Pelanggan Non-Ideal')
plt.suptitle('"Segmen Ini Hampir Kebal Churn, Kenapa Kita Tidak Fokus di Sini?"', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.legend(title='Status Churn', loc='upper right')
plt.show()
# Buat subplot untuk dua pie chart: pelanggan ideal vs non-ideal
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Pie chart untuk pelanggan ideal
axes[0].pie(
perc_ideal_customer['JumlahPelanggan'],
labels=perc_ideal_customer['Churn'],
autopct='%1.1f%%',
startangle=140,
colors=['darkgreen', 'yellow']
)
axes[0].axis('equal')
axes[0].set_title('Segmen Pelanggan Ideal')
# Pie chart untuk pelanggan non-ideal
axes[1].pie(
percNon_ideal_customer['JumlahPelanggan'],
labels=percNon_ideal_customer['Churn'],
autopct='%1.1f%%',
startangle=140,
colors=['yellow', 'darkgreen']
)
axes[1].axis('equal')
axes[1].set_title('Segmen Pelanggan Non-Ideal')
plt.suptitle('"Segmen Ini Hampir Kebal Churn, Kenapa Kita Tidak Fokus di Sini?"', fontsize=14, fontweight='bold', y=1.08)
fig.text(
0.5, 0.98,
'Empat kriteria sederhana kontrak jangka panjang, status pernikahan, masa pakai layanan, dan biaya bulanan \n ternyata mampu mengidentifikasi pelanggan ideal!',
ha='center',
fontsize=11
)
plt.tight_layout()
plt.legend(title='Status Churn', loc='upper right')
plt.show()
Interpretasi¶
Berdasarkan enam visualisasi yang telah disajikan, kita dapat menarik insight penting dari hubungan antara atribut Contract, Status, Tenure, dan Monthly Charges terhadap perilaku Churn pelanggan.
Jenis Kontrak¶
Berikut merupakan plot perbandingan churn berdasarkan jenis kontrak pelanggan.
1. Kontrak Bulanan (Month-to-Month)¶
- Jumlah Churn: Tinggi yaitu 1655 pelanggan
- Jumlah Tidak Churn: Sangat tinggi juga yaitu 2220 pelanggan
Mayoritas pelanggan churn berasal dari kontrak jenis ini, yang menunjukkan bahwa kontrak bulanan bersifat fleksibel, namun kurang mengikat pelanggan untuk tetap bertahan.
Namun menariknya, jumlah pelanggan tidak churn juga paling banyak dalam kelompok ini, yang berarti bahwa kontrak bulanan juga populer secara umum.
Artinya, kontrak ini memiliki churn rate tinggi, tetapi juga memiliki basis pelanggan yang besar.
2. Kontrak Tahunan (One Year)¶
Jumlah Churn: Relatif rendah yaitu 166
Jumlah Tidak Churn: Cukup besar yaitu 1307
Interpretasi:
Pelanggan dengan kontrak satu tahun lebih kecil kemungkinan churn dibanding kontrak bulanan.
Ini menunjukkan bahwa komitmen jangka menengah dapat membantu menurunkan churn rate.
3. Kontrak Dua Tahun (Two Year)¶
Jumlah Churn: Sangat rendah yaitu 48
Jumlah Tidak Churn: Tinggi yaitu 1647
Interpretasi:
Ini adalah tipe kontrak paling stabil.
Pelanggan yang menandatangani kontrak dua tahun hampir semuanya tetap loyal.
Bisa disimpulkan bahwa semakin lama durasi kontrak, semakin kecil risiko churn, karena keterikatan dan insentif loyalitas biasanya lebih tinggi. den sebaliknya pelanggan dengan kontrak singkat seperti Month-to-month memiliki angka churn tertinggi. Ini menandakan bahwa fleksibilitas tinggi pada kontrak bulanan justru mengindikasikan rendahnya loyalitas pelanggan.
Status Pelanggan¶
1. Pelanggan Berstatus Married¶
Memiliki rasio churn yang lebih rendah, yakni sekitar 23,6% dari total pelanggan married yang churn.
Sebaliknya, sekitar 76,4% pelanggan married tetap loyal (tidak churn).
Hal ini menunjukkan bahwa status "married" berkorelasi dengan kecenderungan bertahan lebih tinggi dalam berlangganan layanan.
2. Pelanggan Berstatus Single¶
Memiliki rasio churn yang lebih tinggi, yakni sekitar 31,2% pelanggan single yang churn.
Rasio tidak churn mereka lebih rendah dibandingkan married, sekitar 68,8%.
Menunjukkan bahwa status "single" cenderung lebih rentan terhadap churn.
Pelanggan berstatus Single cenderung lebih banyak churn dilihat dari perbandingan rasio, sementara Married memiliki kecenderungan bertahan lebih lama, menunjukkan hubungan status sosial dengan komitmen layanan.
Tenure (Durasi Berlangganan)¶
1. Pelanggan Tidak Churn¶
Jumlah semakin tinggi seiring bertambahnya tahun.
Kelompok dengan tenure lebih dari 5 tahun mendominasi pelanggan tidak churn (jumlah tertinggi).
Artinya, semakin lama seseorang menjadi pelanggan, semakin kecil kemungkinan mereka churn.
Ini mencerminkan adanya loyalitas dan kepuasan dari pengguna jangka panjang.
2. Pelanggan Churn¶
Churn tertinggi terjadi pada pelanggan ≤1 tahun.
Setelah tahun pertama, jumlah churn menurun drastis dan relatif stabil di kisaran rendah.
Hal ini mengindikasikan bahwa tahun pertama merupakan fase paling kritis bagi pelanggan baru.
Monthly Charges (Tagihan Bulanan)¶
Grafik ini menunjukkan distribusi pelanggan churn dan tidak churn berdasarkan kategori biaya bulanan yang telah dikelompokkan menjadi 3 segmen:
< $40 → Kelompok “Hemat”
$40–80 → Kelompok “Sedang/Mainstream”
.> $80 → Kelompok “Tinggi/Premium”
Pelanggan Tidak Churn¶
Distribusinya relatif seimbang di antara ketiga segmen biaya.
Jumlah tertinggi justru sedikit lebih besar pada kelompok $40–80, diikuti oleh >$80, dan terakhir <$40.
Hal ini menunjukkan bahwa pelanggan dengan biaya tinggi tidak serta merta mudah churn jika mereka puas terhadap layanan.
Bisa disimpulkan bahwa biaya tinggi tidak identik dengan risiko churn selama layanan yang diterima dianggap sesuai.
Pelanggan Churn¶
Jumlah tertinggi berasal dari pelanggan dengan biaya lebih dari $80 per bulan.
Diikuti oleh segmen $40–80, lalu paling sedikit dari kelompok dengan biaya di bawah $40.
Ini menunjukkan bahwa biaya tinggi berpotensi meningkatkan risiko churn terutama jika tidak dibarengi dengan kualitas layanan atau value yang sesuai.
Segmen berbiaya rendah menunjukkan tingkat churn yang sangat kecil, kemungkinan karena pelanggan merasa cocok dengan layanan sesuai harga.
Pelanggan dengan tagihan bulanan tinggi ($80–$120) memiliki kecenderungan lebih besar untuk churn dibandingkan pelanggan dengan biaya rendah. Hal ini menunjukkan bahwa persepsi harga atau ketidakpuasan layanan kemungkinan besar menjadi penyebab.
Implikasi Bisnis¶
Berdasarkan keempat visualisasi yang telah dianalisis sebelumnya yaitu jenis kontrak, status pelanggan, lama berlangganan (tenure), dan biaya bulanan kita dapat menyusun sebuah filter kategorikal untuk mengidentifikasi pelanggan yang berpotensi tinggi melakukan churn maupun tidak, sehingga kita tahu mana yang benar benar pelanggan ideal atau tidak.
Dengan menggabungkan variabel-variabel yang paling dominan memengaruhi churn, diperoleh bahwa :
- kelompok pelanggan dengan jenis kontrak bulanan (Month-to-month),
- berstatus married, memiliki tenure kurang dari 12 bulan, dan
- berada pada rentang biaya bulanan antara $80 hingga $120,
merupakan segmen dengan jumlah pelanggan churn yang paling signifikan.
Ketika dilakukan penyaringan menggunakan keempat kriteria tersebut secara bersamaan, diperoleh bahwa terdapat 161 (74.88%) pelanggan yang churn, dibandingkan dengan hanya 54 (25.12%) pelanggan yang tidak churn, sehingga akurasi prediktif dari segmen ini sangat tinggi dan layak untuk menjadi target prioritas dalam strategi retensi pelanggan.¶
Dengan demikian, akurasi prediktif dari segmen ini mencapai hampir 75%, yang menunjukkan bahwa strategi penyaringan sederhana berbasis aturan logis sudah cukup efektif dalam mengidentifikasi kelompok berisiko tinggi, tanpa perlu membangun model prediktif yang kompleks. Segmen ini sangat layak dijadikan target prioritas dalam program retensi pelanggan, misalnya dengan pemberian insentif atau penawaran kontrak jangka panjang.
Sedangkan untuk kategori yang tidak churn /non churn bisa kita lakukan analisis lagi lebih mendalam sehingga mendapatkan hasil penyaringan terhadap segmen pelanggan ideal yaitu
- pelanggan dengan tenure lebih dari 2 tahun,
- kontrak dua tahun, status married, dan
- biaya bulanan antara $40–$80
Ditemukan bahwa dari total 279 pelanggan, sebanyak 270 pelanggan tidak churn (96.77%) dan hanya 9 pelanggan yang churn (3.23%).¶
Temuan ini menunjukkan bahwa segmen ini merupakan pelanggan paling loyal dan stabil, dengan tingkat churn yang sangat rendah. Pelanggan seperti ini patut dikategorikan sebagai “low-risk segment” dan bisa menjadi acuan untuk mencari prospek serupa di masa depan.
Selain itu, profil ini juga dapat dijadikan role model dalam kampanye pemasaran (customer persona), atau bahkan digunakan dalam strategi targeting untuk menjaring pelanggan baru dengan karakteristik serupa.
filtered = agung[(agung['Contract'] == 'Month-to-month') & (agung['Status'] == 'Married') &(agung['tenure'] < 12) &(agung['MonthlyCharges'] >= 80) & (agung['MonthlyCharges'] <= 120)]
hasil = filtered['Churn'].value_counts().reset_index()
hasil.columns = ['Churn', 'Jumlah']
print(hasil)
Churn Jumlah 0 Yes 161 1 No 54
filtered = agung[(agung['Contract'] == 'Month-to-month') &(agung['Status'] == 'Married') &(agung['tenure'] < 12) &(agung['MonthlyCharges'] >= 80) & agung['MonthlyCharges'] <= 120)]
hasil = filtered['Churn'].value_counts().reset_index()
hasil.columns = ['Churn', 'Jumlah']
total = hasil['Jumlah'].sum()
hasil['Rasio (%)'] = (hasil['Jumlah'] / total * 100).round(2)
print(hasil)
Churn Jumlah Rasio (%) 0 Yes 161 74.88 1 No 54 25.12
ideal_customer = agung[(agung['tenure'] > 24) & (agung['Contract'] == 'Two year') & (agung['Status'] == 'Married') & (agung['MonthlyCharges'].between(40, 80))]
status_ideal_customer = ideal_customer['Churn'].value_counts().reset_index()
status_ideal_customer.columns = ['Churn', 'JumlahPelanggan']
status_ideal_customer
| Churn | JumlahPelanggan | |
|---|---|---|
| 0 | No | 270 |
| 1 | Yes | 9 |